SSH SFTP Updater Support - Version 0.8.4

Version Description

  • 2020/Dec/30 =

  • TWEAK: Remove obsolete references to other plugins

  • TWEAK: Replace some further deprecated jQuery styles

  • TWEAK: Update to latest 1.0.x version of phpseclib

Download this release

Release Info

Developer DavidAnderson
Plugin Icon wp plugin SSH SFTP Updater Support
Version 0.8.4
Comparing to
See all releases

Code changes from version 0.8.3 to 0.8.4

includes/ssh-sftp-updater-support-notices.php CHANGED
@@ -68,28 +68,6 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
68
  'supported_positions' => $this->anywhere,
69
  'validity_function' => 'is_wpo_installed',
70
  ),
71
- 'keyy' => array(
72
- 'prefix' => '',
73
- 'title' => 'Keyy: Instant and secure logon with a wave of your phone',
74
- 'text' => __("Find out more about our revolutionary new WordPress plugin.", "ssh-sftp-updater-support"),
75
- 'button_link' => 'https://getkeyy.com/',
76
- 'button_meta' => 'keyy',
77
- 'image' => 'notices/keyy_logo.png',
78
- 'dismiss_time' => 'dismiss_page_notice_until',
79
- 'supported_positions' => $this->anywhere,
80
- 'validity_function' => 'is_keyy_installed',
81
- ),
82
- 'meta-slider' => array(
83
- 'prefix' => '',
84
- 'title' => __("MetaSlider: the world's #1 slider plugin from the makers of SSH SFTP Updater Support", "ssh-sftp-updater-support"),
85
- 'text' => __("With MetaSlider, you can easily add style and flare with beautifully-designed sliders.", "ssh-sftp-updater-support"),
86
- 'button_link' => 'https://metaslider.com',
87
- 'button_meta' => 'metaslider',
88
- 'image' => 'notices/metaslider_logo.png',
89
- 'dismiss_time' => 'dismiss_page_notice_until',
90
- 'supported_positions' => $this->anywhere,
91
- 'validity_function' => 'is_metaslider_installed',
92
- ),
93
 
94
  );
95
 
@@ -130,17 +108,6 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
130
  return parent::is_plugin_installed($product, $also_require_active);
131
  }
132
 
133
- /**
134
- * This method will call the parent is_plugin_installed and pass in the product keyy to check if that plugin is installed if it is then we shouldn't display the notice
135
- *
136
- * @param string $product the plugin slug
137
- * @param boolean $also_require_active a bool to indicate if the plugin should also be active
138
- * @return boolean a bool to indicate if the notice should be displayed or not
139
- */
140
- protected function is_keyy_installed($product = 'keyy', $also_require_active = false) {
141
- return parent::is_plugin_installed($product, $also_require_active);
142
- }
143
-
144
  /**
145
  * This method will call the is premium function in the SSH_SFTP_Updater_Support_Notices object to check if this install is premium and if it is we won't display the notice
146
  *
@@ -149,19 +116,7 @@ class SSH_SFTP_Updater_Support_Notices extends Updraft_Notices_1_0 {
149
  protected function is_wpo_installed($product = 'wp-optimize', $also_require_active = false) {
150
  return parent::is_plugin_installed($product, $also_require_active);
151
  }
152
-
153
- /**
154
- * This method will check to see if Meta Slider plugin is installed.
155
- *
156
- * @param String $product the plugin slug
157
- * @param boolean $also_require_active a bool to indicate if the plugin should be active or not
158
- * @return boolean a bool to indicate if the notice should be displayed or not
159
- */
160
- protected function is_metaslider_installed($product = 'ml-slider', $also_require_active = false) {
161
- return parent::is_plugin_installed($product, $also_require_active);
162
- }
163
-
164
-
165
  /**
166
  * This method calls the parent verson and will work out if the user is using a non english language and if so returns true so that they can see the translation advert.
167
  *
68
  'supported_positions' => $this->anywhere,
69
  'validity_function' => 'is_wpo_installed',
70
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  );
73
 
108
  return parent::is_plugin_installed($product, $also_require_active);
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
111
  /**
112
  * This method will call the is premium function in the SSH_SFTP_Updater_Support_Notices object to check if this install is premium and if it is we won't display the notice
113
  *
116
  protected function is_wpo_installed($product = 'wp-optimize', $also_require_active = false) {
117
  return parent::is_plugin_installed($product, $also_require_active);
118
  }
119
+
 
 
 
 
 
 
 
 
 
 
 
 
120
  /**
121
  * This method calls the parent verson and will work out if the user is using a non english language and if so returns true so that they can see the translation advert.
122
  *
phpseclib/Crypt/RSA.php CHANGED
@@ -1603,12 +1603,12 @@ class Crypt_RSA
1603
  &$components['primes'][2]
1604
  );
1605
 
1606
- foreach ($values as &$value) {
1607
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
1608
  if (strlen($paddedKey) < $length) {
1609
  return false;
1610
  }
1611
- $value = new Math_BigInteger($this->_string_shift($paddedKey, $length), -256);
1612
  }
1613
 
1614
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
@@ -2846,7 +2846,7 @@ class Crypt_RSA
2846
  // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
2847
  // be output.
2848
 
2849
- $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
2850
  $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
2851
 
2852
  $mHash = $this->hash->hash($m);
@@ -2924,7 +2924,7 @@ class Crypt_RSA
2924
 
2925
  // RSA verification
2926
 
2927
- $modBits = 8 * $this->k;
2928
 
2929
  $s2 = $this->_os2ip($s);
2930
  $m2 = $this->_rsavp1($s2);
@@ -2932,7 +2932,7 @@ class Crypt_RSA
2932
  user_error('Invalid signature');
2933
  return false;
2934
  }
2935
- $em = $this->_i2osp($m2, $modBits >> 3);
2936
  if ($em === false) {
2937
  user_error('Invalid signature');
2938
  return false;
1603
  &$components['primes'][2]
1604
  );
1605
 
1606
+ for ($i = 0; $i < count($values); $i++) {
1607
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
1608
  if (strlen($paddedKey) < $length) {
1609
  return false;
1610
  }
1611
+ $values[$i] = new Math_BigInteger($this->_string_shift($paddedKey, $length), -256);
1612
  }
1613
 
1614
  extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
2846
  // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
2847
  // be output.
2848
 
2849
+ $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
2850
  $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
2851
 
2852
  $mHash = $this->hash->hash($m);
2924
 
2925
  // RSA verification
2926
 
2927
+ $modBits = strlen($this->modulus->toBits());
2928
 
2929
  $s2 = $this->_os2ip($s);
2930
  $m2 = $this->_rsavp1($s2);
2932
  user_error('Invalid signature');
2933
  return false;
2934
  }
2935
+ $em = $this->_i2osp($m2, $this->k);
2936
  if ($em === false) {
2937
  user_error('Invalid signature');
2938
  return false;
phpseclib/Crypt/Random.php CHANGED
@@ -105,7 +105,10 @@ if (!function_exists('crypt_random_string')) {
105
  $fp = @fopen('/dev/urandom', 'rb');
106
  }
107
  if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
108
- return fread($fp, $length);
 
 
 
109
  }
110
  // method 3. pretty much does the same thing as method 2 per the following url:
111
  // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
105
  $fp = @fopen('/dev/urandom', 'rb');
106
  }
107
  if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
108
+ $temp = fread($fp, $length);
109
+ if (strlen($temp) == $length) {
110
+ return $temp;
111
+ }
112
  }
113
  // method 3. pretty much does the same thing as method 2 per the following url:
114
  // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
phpseclib/File/ANSI.php CHANGED
@@ -299,7 +299,7 @@ class File_ANSI
299
  case "\x1B[K": // Clear screen from cursor right
300
  $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
301
 
302
- array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));
303
  break;
304
  case "\x1B[2K": // Clear entire line
305
  $this->screen[$this->y] = str_repeat(' ', $this->x);
299
  case "\x1B[K": // Clear screen from cursor right
300
  $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
301
 
302
+ array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - ($this->x - 1), $this->base_attr_cell));
303
  break;
304
  case "\x1B[2K": // Clear entire line
305
  $this->screen[$this->y] = str_repeat(' ', $this->x);
phpseclib/File/ASN1.php CHANGED
@@ -577,6 +577,10 @@ class File_ASN1
577
  */
578
  function asn1map($decoded, $mapping, $special = array())
579
  {
 
 
 
 
580
  if (isset($mapping['explicit']) && is_array($decoded['content'])) {
581
  $decoded = $decoded['content'][0];
582
  }
@@ -787,7 +791,14 @@ class File_ASN1
787
  case FILE_ASN1_TYPE_UTC_TIME:
788
  case FILE_ASN1_TYPE_GENERALIZED_TIME:
789
  if (class_exists('DateTime')) {
790
- if (isset($mapping['implicit'])) {
 
 
 
 
 
 
 
791
  $decoded['content'] = $this->_decodeDateTime($decoded['content'], $decoded['type']);
792
  }
793
  if (!$decoded['content']) {
@@ -795,7 +806,10 @@ class File_ASN1
795
  }
796
  return $decoded['content']->format($this->format);
797
  } else {
798
- if (isset($mapping['implicit'])) {
 
 
 
799
  $decoded['content'] = $this->_decodeUnixTime($decoded['content'], $decoded['type']);
800
  }
801
  return @date($this->format, $decoded['content']);
@@ -937,7 +951,7 @@ class File_ASN1
937
  if ($mapping['type'] == FILE_ASN1_TYPE_SET) {
938
  sort($value);
939
  }
940
- $value = implode($value, '');
941
  break;
942
  }
943
 
577
  */
578
  function asn1map($decoded, $mapping, $special = array())
579
  {
580
+ if (!is_array($decoded)) {
581
+ return false;
582
+ }
583
+
584
  if (isset($mapping['explicit']) && is_array($decoded['content'])) {
585
  $decoded = $decoded['content'][0];
586
  }
791
  case FILE_ASN1_TYPE_UTC_TIME:
792
  case FILE_ASN1_TYPE_GENERALIZED_TIME:
793
  if (class_exists('DateTime')) {
794
+ // for explicitly tagged optional stuff
795
+ if (is_array($decoded['content'])) {
796
+ $decoded['content'] = $decoded['content'][0]['content'];
797
+ }
798
+ // for implicitly tagged optional stuff
799
+ // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
800
+ // in the wild that OpenSSL decodes without issue so we'll support them as well
801
+ if (!is_object($decoded['content'])) {
802
  $decoded['content'] = $this->_decodeDateTime($decoded['content'], $decoded['type']);
803
  }
804
  if (!$decoded['content']) {
806
  }
807
  return $decoded['content']->format($this->format);
808
  } else {
809
+ if (is_array($decoded['content'])) {
810
+ $decoded['content'] = $decoded['content'][0]['content'];
811
+ }
812
+ if (!is_int($decoded['content'])) {
813
  $decoded['content'] = $this->_decodeUnixTime($decoded['content'], $decoded['type']);
814
  }
815
  return @date($this->format, $decoded['content']);
951
  if ($mapping['type'] == FILE_ASN1_TYPE_SET) {
952
  sort($value);
953
  }
954
+ $value = implode('', $value);
955
  break;
956
  }
957
 
phpseclib/File/X509.php CHANGED
@@ -982,6 +982,13 @@ class File_X509
982
  'children' => $AccessDescription
983
  );
984
 
 
 
 
 
 
 
 
985
  $this->SubjectAltName = $GeneralNames;
986
 
987
  $this->PrivateKeyUsagePeriod = array(
@@ -1650,7 +1657,10 @@ class File_X509
1650
  corresponding to the extension type identified by extnID */
1651
  $map = $this->_getMapping($id);
1652
  if (!is_bool($map)) {
1653
- $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
 
 
 
1654
  $value = $mapped === false ? $decoded[0] : $mapped;
1655
 
1656
  if ($id == 'id-ce-certificatePolicies') {
@@ -1919,6 +1929,8 @@ class File_X509
1919
  return $this->ExtKeyUsageSyntax;
1920
  case 'id-pe-authorityInfoAccess':
1921
  return $this->AuthorityInfoAccessSyntax;
 
 
1922
  case 'id-ce-subjectAltName':
1923
  return $this->SubjectAltName;
1924
  case 'id-ce-subjectDirectoryAttributes':
@@ -2206,7 +2218,11 @@ class File_X509
2206
  }
2207
 
2208
  while (!feof($fsock)) {
2209
- $data.= fread($fsock, 1024);
 
 
 
 
2210
  }
2211
 
2212
  break;
@@ -2255,7 +2271,7 @@ class File_X509
2255
  return false;
2256
  }
2257
 
2258
- $parent = new static();
2259
  $parent->CAs = $this->CAs;
2260
  /*
2261
  "Conforming applications that support HTTP or FTP for accessing
@@ -2555,18 +2571,36 @@ class File_X509
2555
  return long2ip($ip);
2556
  }
2557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2558
  /**
2559
  * Encodes an IP address
2560
  *
2561
  * Takes a human readable IP address into a base64-encoded "blob"
2562
  *
2563
- * @param string $ip
2564
  * @access private
2565
  * @return string
2566
  */
2567
  function _encodeIP($ip)
2568
  {
2569
- return base64_encode(pack('N', ip2long($ip)));
 
 
2570
  }
2571
 
2572
  /**
@@ -2970,7 +3004,7 @@ class File_X509
2970
  } elseif (is_object($value) && strtolower(get_class($value)) == 'file_asn1_element') {
2971
  // @codingStandardsIgnoreStart
2972
  $callback = version_compare(PHP_VERSION, '5.3.0') >= 0 ?
2973
- function ($x) { return "\x" . bin2hex($x[0]); } :
2974
  create_function('$x', 'return "\x" . bin2hex($x[0]);');
2975
  // @codingStandardsIgnoreEnd
2976
  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
982
  'children' => $AccessDescription
983
  );
984
 
985
+ $this->SubjectInfoAccessSyntax = array(
986
+ 'type' => FILE_ASN1_TYPE_SEQUENCE,
987
+ 'min' => 1,
988
+ 'max' => -1,
989
+ 'children' => $AccessDescription
990
+ );
991
+
992
  $this->SubjectAltName = $GeneralNames;
993
 
994
  $this->PrivateKeyUsagePeriod = array(
1657
  corresponding to the extension type identified by extnID */
1658
  $map = $this->_getMapping($id);
1659
  if (!is_bool($map)) {
1660
+ $decoder = $id == 'id-ce-nameConstraints' ?
1661
+ array($this, '_decodeNameConstraintIP') :
1662
+ array($this, '_decodeIP');
1663
+ $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder));
1664
  $value = $mapped === false ? $decoded[0] : $mapped;
1665
 
1666
  if ($id == 'id-ce-certificatePolicies') {
1929
  return $this->ExtKeyUsageSyntax;
1930
  case 'id-pe-authorityInfoAccess':
1931
  return $this->AuthorityInfoAccessSyntax;
1932
+ case 'id-pe-subjectInfoAccess':
1933
+ return $this->SubjectInfoAccessSyntax;
1934
  case 'id-ce-subjectAltName':
1935
  return $this->SubjectAltName;
1936
  case 'id-ce-subjectDirectoryAttributes':
2218
  }
2219
 
2220
  while (!feof($fsock)) {
2221
+ $temp = fread($fsock, 1024);
2222
+ if ($temp === false) {
2223
+ return false;
2224
+ }
2225
+ $data.= $temp;
2226
  }
2227
 
2228
  break;
2271
  return false;
2272
  }
2273
 
2274
+ $parent = new File_X509();
2275
  $parent->CAs = $this->CAs;
2276
  /*
2277
  "Conforming applications that support HTTP or FTP for accessing
2571
  return long2ip($ip);
2572
  }
2573
 
2574
+ /**
2575
+ * Decodes an IP address in a name constraints extension
2576
+ *
2577
+ * Takes in a base64 encoded "blob" and returns a human readable IP address / mask
2578
+ *
2579
+ * @param string $ip
2580
+ * @access private
2581
+ * @return array
2582
+ */
2583
+ function _decodeNameConstraintIP($ip)
2584
+ {
2585
+ $ip = base64_decode($ip);
2586
+ list(, $ip, $mask) = unpack('N2', $ip);
2587
+ return array(long2ip($ip), long2ip($mask));
2588
+ }
2589
+
2590
  /**
2591
  * Encodes an IP address
2592
  *
2593
  * Takes a human readable IP address into a base64-encoded "blob"
2594
  *
2595
+ * @param string|array $ip
2596
  * @access private
2597
  * @return string
2598
  */
2599
  function _encodeIP($ip)
2600
  {
2601
+ return is_string($ip) ?
2602
+ base64_encode(pack('N', ip2long($ip))) :
2603
+ base64_encode(pack('NN', ip2long($ip[0]), ip2long($ip[1])));
2604
  }
2605
 
2606
  /**
3004
  } elseif (is_object($value) && strtolower(get_class($value)) == 'file_asn1_element') {
3005
  // @codingStandardsIgnoreStart
3006
  $callback = version_compare(PHP_VERSION, '5.3.0') >= 0 ?
3007
+ eval('return function ($x) { return "\x" . bin2hex($x[0]); };') :
3008
  create_function('$x', 'return "\x" . bin2hex($x[0]);');
3009
  // @codingStandardsIgnoreEnd
3010
  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
phpseclib/Math/BigInteger.php CHANGED
@@ -556,7 +556,7 @@ class Math_BigInteger
556
  $bytes = chr(0);
557
  }
558
 
559
- if (ord($bytes[0]) & 0x80) {
560
  $bytes = chr(0) . $bytes;
561
  }
562
 
@@ -724,6 +724,7 @@ class Math_BigInteger
724
  }
725
 
726
  $temp = $this->copy();
 
727
  $temp->is_negative = false;
728
 
729
  $divisor = new Math_BigInteger();
@@ -860,7 +861,7 @@ class Math_BigInteger
860
  $opts[] = 'OpenSSL';
861
  }
862
  if (!empty($opts)) {
863
- $engine.= ' (' . implode($opts, ', ') . ')';
864
  }
865
  return array(
866
  'value' => '0x' . $this->toHex(true),
@@ -1586,7 +1587,9 @@ class Math_BigInteger
1586
  $temp_value = array($quotient_value[$q_index]);
1587
  $temp = $temp->multiply($y);
1588
  $temp_value = &$temp->value;
1589
- $temp_value = array_merge($adjust, $temp_value);
 
 
1590
 
1591
  $x = $x->subtract($temp);
1592
 
@@ -3613,7 +3616,14 @@ class Math_BigInteger
3613
  switch (MATH_BIGINTEGER_MODE) {
3614
  case MATH_BIGINTEGER_MODE_GMP:
3615
  if ($this->bitmask !== false) {
 
 
 
 
3616
  $result->value = gmp_and($result->value, $result->bitmask->value);
 
 
 
3617
  }
3618
 
3619
  return $result;
@@ -3628,6 +3638,7 @@ class Math_BigInteger
3628
  $value = &$result->value;
3629
 
3630
  if (!count($value)) {
 
3631
  return $result;
3632
  }
3633
 
556
  $bytes = chr(0);
557
  }
558
 
559
+ if ($this->precision <= 0 && (ord($bytes[0]) & 0x80)) {
560
  $bytes = chr(0) . $bytes;
561
  }
562
 
724
  }
725
 
726
  $temp = $this->copy();
727
+ $temp->bitmask = false;
728
  $temp->is_negative = false;
729
 
730
  $divisor = new Math_BigInteger();
861
  $opts[] = 'OpenSSL';
862
  }
863
  if (!empty($opts)) {
864
+ $engine.= ' (' . implode('.', $opts) . ')';
865
  }
866
  return array(
867
  'value' => '0x' . $this->toHex(true),
1587
  $temp_value = array($quotient_value[$q_index]);
1588
  $temp = $temp->multiply($y);
1589
  $temp_value = &$temp->value;
1590
+ if (count($temp_value)) {
1591
+ $temp_value = array_merge($adjust, $temp_value);
1592
+ }
1593
 
1594
  $x = $x->subtract($temp);
1595
 
3616
  switch (MATH_BIGINTEGER_MODE) {
3617
  case MATH_BIGINTEGER_MODE_GMP:
3618
  if ($this->bitmask !== false) {
3619
+ $flip = gmp_cmp($result->value, gmp_init(0)) < 0;
3620
+ if ($flip) {
3621
+ $result->value = gmp_neg($result->value);
3622
+ }
3623
  $result->value = gmp_and($result->value, $result->bitmask->value);
3624
+ if ($flip) {
3625
+ $result->value = gmp_neg($result->value);
3626
+ }
3627
  }
3628
 
3629
  return $result;
3638
  $value = &$result->value;
3639
 
3640
  if (!count($value)) {
3641
+ $result->is_negative = false;
3642
  return $result;
3643
  }
3644
 
phpseclib/Net/SFTP.php CHANGED
@@ -1,3206 +1,3240 @@
1
- <?php
2
-
3
- /**
4
- * Pure-PHP implementation of SFTP.
5
- *
6
- * PHP versions 4 and 5
7
- *
8
- * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
9
- * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
10
- * to an SFTPv4/5/6 server.
11
- *
12
- * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
13
- *
14
- * Here's a short example of how to use this library:
15
- * <code>
16
- * <?php
17
- * include 'Net/SFTP.php';
18
- *
19
- * $sftp = new Net_SFTP('www.domain.tld');
20
- * if (!$sftp->login('username', 'password')) {
21
- * exit('Login Failed');
22
- * }
23
- *
24
- * echo $sftp->pwd() . "\r\n";
25
- * $sftp->put('filename.ext', 'hello, world!');
26
- * print_r($sftp->nlist());
27
- * ?>
28
- * </code>
29
- *
30
- * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
31
- * of this software and associated documentation files (the "Software"), to deal
32
- * in the Software without restriction, including without limitation the rights
33
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
- * copies of the Software, and to permit persons to whom the Software is
35
- * furnished to do so, subject to the following conditions:
36
- *
37
- * The above copyright notice and this permission notice shall be included in
38
- * all copies or substantial portions of the Software.
39
- *
40
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
- * THE SOFTWARE.
47
- *
48
- * @category Net
49
- * @package Net_SFTP
50
- * @author Jim Wigginton <terrafrost@php.net>
51
- * @copyright 2009 Jim Wigginton
52
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
53
- * @link http://phpseclib.sourceforge.net
54
- */
55
-
56
- /**
57
- * Include Net_SSH2
58
- */
59
- if (!class_exists('Net_SSH2')) {
60
- include_once 'SSH2.php';
61
- }
62
-
63
- /**#@+
64
- * @access public
65
- * @see self::getLog()
66
- */
67
- /**
68
- * Returns the message numbers
69
- */
70
- define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
71
- /**
72
- * Returns the message content
73
- */
74
- define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
75
- /**
76
- * Outputs the message content in real-time.
77
- */
78
- define('NET_SFTP_LOG_REALTIME', 3);
79
- /**#@-*/
80
-
81
- /**
82
- * SFTP channel constant
83
- *
84
- * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
85
- *
86
- * @see Net_SSH2::_send_channel_packet()
87
- * @see Net_SSH2::_get_channel_packet()
88
- * @access private
89
- */
90
- define('NET_SFTP_CHANNEL', 0x100);
91
-
92
- /**#@+
93
- * @access public
94
- * @see self::put()
95
- */
96
- /**
97
- * Reads data from a local file.
98
- */
99
- define('NET_SFTP_LOCAL_FILE', 1);
100
- /**
101
- * Reads data from a string.
102
- */
103
- // this value isn't really used anymore but i'm keeping it reserved for historical reasons
104
- define('NET_SFTP_STRING', 2);
105
- /**
106
- * Reads data from callback:
107
- * function callback($length) returns string to proceed, null for EOF
108
- */
109
- define('NET_SFTP_CALLBACK', 16);
110
- /**
111
- * Resumes an upload
112
- */
113
- define('NET_SFTP_RESUME', 4);
114
- /**
115
- * Append a local file to an already existing remote file
116
- */
117
- define('NET_SFTP_RESUME_START', 8);
118
- /**#@-*/
119
-
120
- /**
121
- * Pure-PHP implementations of SFTP.
122
- *
123
- * @package Net_SFTP
124
- * @author Jim Wigginton <terrafrost@php.net>
125
- * @access public
126
- */
127
- class Net_SFTP extends Net_SSH2
128
- {
129
- /**
130
- * Packet Types
131
- *
132
- * @see self::Net_SFTP()
133
- * @var array
134
- * @access private
135
- */
136
- var $packet_types = array();
137
-
138
- /**
139
- * Status Codes
140
- *
141
- * @see self::Net_SFTP()
142
- * @var array
143
- * @access private
144
- */
145
- var $status_codes = array();
146
-
147
- /**
148
- * The Request ID
149
- *
150
- * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
151
- * concurrent actions, so it's somewhat academic, here.
152
- *
153
- * @var boolean
154
- * @see self::_send_sftp_packet()
155
- * @access private
156
- */
157
- var $use_request_id = false;
158
-
159
- /**
160
- * The Packet Type
161
- *
162
- * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
163
- * concurrent actions, so it's somewhat academic, here.
164
- *
165
- * @var int
166
- * @see self::_get_sftp_packet()
167
- * @access private
168
- */
169
- var $packet_type = -1;
170
-
171
- /**
172
- * Packet Buffer
173
- *
174
- * @var string
175
- * @see self::_get_sftp_packet()
176
- * @access private
177
- */
178
- var $packet_buffer = '';
179
-
180
- /**
181
- * Extensions supported by the server
182
- *
183
- * @var array
184
- * @see self::_initChannel()
185
- * @access private
186
- */
187
- var $extensions = array();
188
-
189
- /**
190
- * Server SFTP version
191
- *
192
- * @var int
193
- * @see self::_initChannel()
194
- * @access private
195
- */
196
- var $version;
197
-
198
- /**
199
- * Current working directory
200
- *
201
- * @var string
202
- * @see self::realpath()
203
- * @see self::chdir()
204
- * @access private
205
- */
206
- var $pwd = false;
207
-
208
- /**
209
- * Packet Type Log
210
- *
211
- * @see self::getLog()
212
- * @var array
213
- * @access private
214
- */
215
- var $packet_type_log = array();
216
-
217
- /**
218
- * Packet Log
219
- *
220
- * @see self::getLog()
221
- * @var array
222
- * @access private
223
- */
224
- var $packet_log = array();
225
-
226
- /**
227
- * Error information
228
- *
229
- * @see self::getSFTPErrors()
230
- * @see self::getLastSFTPError()
231
- * @var array
232
- * @access private
233
- */
234
- var $sftp_errors = array();
235
-
236
- /**
237
- * Stat Cache
238
- *
239
- * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
240
- * we'll cache the results.
241
- *
242
- * @see self::_update_stat_cache()
243
- * @see self::_remove_from_stat_cache()
244
- * @see self::_query_stat_cache()
245
- * @var array
246
- * @access private
247
- */
248
- var $stat_cache = array();
249
-
250
- /**
251
- * Max SFTP Packet Size
252
- *
253
- * @see self::Net_SFTP()
254
- * @see self::get()
255
- * @var array
256
- * @access private
257
- */
258
- var $max_sftp_packet;
259
-
260
- /**
261
- * Stat Cache Flag
262
- *
263
- * @see self::disableStatCache()
264
- * @see self::enableStatCache()
265
- * @var bool
266
- * @access private
267
- */
268
- var $use_stat_cache = true;
269
-
270
- /**
271
- * Sort Options
272
- *
273
- * @see self::_comparator()
274
- * @see self::setListOrder()
275
- * @var array
276
- * @access private
277
- */
278
- var $sortOptions = array();
279
-
280
- /**
281
- * Canonicalization Flag
282
- *
283
- * Determines whether or not paths should be canonicalized before being
284
- * passed on to the remote server.
285
- *
286
- * @see self::enablePathCanonicalization()
287
- * @see self::disablePathCanonicalization()
288
- * @see self::realpath()
289
- * @var bool
290
- * @access private
291
- */
292
- var $canonicalize_paths = true;
293
-
294
- /**
295
- * Request Buffers
296
- *
297
- * @see self::_get_sftp_packet()
298
- * @var array
299
- * @access private
300
- */
301
- var $requestBuffer = array();
302
-
303
- /**
304
- * Default Constructor.
305
- *
306
- * Connects to an SFTP server
307
- *
308
- * @param string $host
309
- * @param int $port
310
- * @param int $timeout
311
- * @return Net_SFTP
312
- * @access public
313
- */
314
- function __construct($host, $port = 22, $timeout = 10)
315
- {
316
- parent::__construct($host, $port, $timeout);
317
-
318
- $this->max_sftp_packet = 1 << 15;
319
-
320
- $this->packet_types = array(
321
- 1 => 'NET_SFTP_INIT',
322
- 2 => 'NET_SFTP_VERSION',
323
- /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
324
- SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
325
- pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
326
- 3 => 'NET_SFTP_OPEN',
327
- 4 => 'NET_SFTP_CLOSE',
328
- 5 => 'NET_SFTP_READ',
329
- 6 => 'NET_SFTP_WRITE',
330
- 7 => 'NET_SFTP_LSTAT',
331
- 9 => 'NET_SFTP_SETSTAT',
332
- 11 => 'NET_SFTP_OPENDIR',
333
- 12 => 'NET_SFTP_READDIR',
334
- 13 => 'NET_SFTP_REMOVE',
335
- 14 => 'NET_SFTP_MKDIR',
336
- 15 => 'NET_SFTP_RMDIR',
337
- 16 => 'NET_SFTP_REALPATH',
338
- 17 => 'NET_SFTP_STAT',
339
- /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
340
- SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
341
- pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
342
- 18 => 'NET_SFTP_RENAME',
343
- 19 => 'NET_SFTP_READLINK',
344
- 20 => 'NET_SFTP_SYMLINK',
345
-
346
- 101=> 'NET_SFTP_STATUS',
347
- 102=> 'NET_SFTP_HANDLE',
348
- /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
349
- SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
350
- pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
351
- 103=> 'NET_SFTP_DATA',
352
- 104=> 'NET_SFTP_NAME',
353
- 105=> 'NET_SFTP_ATTRS',
354
-
355
- 200=> 'NET_SFTP_EXTENDED'
356
- );
357
- $this->status_codes = array(
358
- 0 => 'NET_SFTP_STATUS_OK',
359
- 1 => 'NET_SFTP_STATUS_EOF',
360
- 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
361
- 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
362
- 4 => 'NET_SFTP_STATUS_FAILURE',
363
- 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
364
- 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
365
- 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
366
- 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
367
- 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
368
- 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
369
- 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
370
- 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
371
- 13 => 'NET_SFTP_STATUS_NO_MEDIA',
372
- 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
373
- 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
374
- 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
375
- 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
376
- 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
377
- 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
378
- 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
379
- 21 => 'NET_SFTP_STATUS_LINK_LOOP',
380
- 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
381
- 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
382
- 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
383
- 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
384
- 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
385
- 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
386
- 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
387
- 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
388
- 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
389
- 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
390
- );
391
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
392
- // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
393
- $this->attributes = array(
394
- 0x00000001 => 'NET_SFTP_ATTR_SIZE',
395
- 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
396
- 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
397
- 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
398
- // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
399
- // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
400
- // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
401
- // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
402
- (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
403
- );
404
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
405
- // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
406
- // the array for that $this->open5_flags and similarly alter the constant names.
407
- $this->open_flags = array(
408
- 0x00000001 => 'NET_SFTP_OPEN_READ',
409
- 0x00000002 => 'NET_SFTP_OPEN_WRITE',
410
- 0x00000004 => 'NET_SFTP_OPEN_APPEND',
411
- 0x00000008 => 'NET_SFTP_OPEN_CREATE',
412
- 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
413
- 0x00000020 => 'NET_SFTP_OPEN_EXCL'
414
- );
415
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
416
- // see Net_SFTP::_parseLongname() for an explanation
417
- $this->file_types = array(
418
- 1 => 'NET_SFTP_TYPE_REGULAR',
419
- 2 => 'NET_SFTP_TYPE_DIRECTORY',
420
- 3 => 'NET_SFTP_TYPE_SYMLINK',
421
- 4 => 'NET_SFTP_TYPE_SPECIAL',
422
- 5 => 'NET_SFTP_TYPE_UNKNOWN',
423
- // the followin types were first defined for use in SFTPv5+
424
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
425
- 6 => 'NET_SFTP_TYPE_SOCKET',
426
- 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
427
- 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
428
- 9 => 'NET_SFTP_TYPE_FIFO'
429
- );
430
- $this->_define_array(
431
- $this->packet_types,
432
- $this->status_codes,
433
- $this->attributes,
434
- $this->open_flags,
435
- $this->file_types
436
- );
437
-
438
- if (!defined('NET_SFTP_QUEUE_SIZE')) {
439
- define('NET_SFTP_QUEUE_SIZE', 32);
440
- }
441
- }
442
-
443
- /**
444
- * PHP4 compatible Default Constructor.
445
- *
446
- * @see self::__construct()
447
- * @param string $host
448
- * @param int $port
449
- * @param int $timeout
450
- * @access public
451
- */
452
- function Net_SFTP($host, $port = 22, $timeout = 10)
453
- {
454
- $this->__construct($host, $port, $timeout);
455
- }
456
-
457
- /**
458
- * Login
459
- *
460
- * @param string $username
461
- * @param string $password
462
- * @return bool
463
- * @access public
464
- */
465
- function login($username)
466
- {
467
- $args = func_get_args();
468
- if (!call_user_func_array(array(&$this, '_login'), $args)) {
469
- return false;
470
- }
471
-
472
- $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
473
-
474
- $packet = pack(
475
- 'CNa*N3',
476
- NET_SSH2_MSG_CHANNEL_OPEN,
477
- strlen('session'),
478
- 'session',
479
- NET_SFTP_CHANNEL,
480
- $this->window_size,
481
- 0x4000
482
- );
483
-
484
- if (!$this->_send_binary_packet($packet)) {
485
- return false;
486
- }
487
-
488
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
489
-
490
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
491
- if ($response === false) {
492
- return false;
493
- }
494
-
495
- $packet = pack(
496
- 'CNNa*CNa*',
497
- NET_SSH2_MSG_CHANNEL_REQUEST,
498
- $this->server_channels[NET_SFTP_CHANNEL],
499
- strlen('subsystem'),
500
- 'subsystem',
501
- 1,
502
- strlen('sftp'),
503
- 'sftp'
504
- );
505
- if (!$this->_send_binary_packet($packet)) {
506
- return false;
507
- }
508
-
509
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
510
-
511
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
512
- if ($response === false) {
513
- // from PuTTY's psftp.exe
514
- $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
515
- "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
516
- "exec sftp-server";
517
- // 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
518
- // is redundant
519
- $packet = pack(
520
- 'CNNa*CNa*',
521
- NET_SSH2_MSG_CHANNEL_REQUEST,
522
- $this->server_channels[NET_SFTP_CHANNEL],
523
- strlen('exec'),
524
- 'exec',
525
- 1,
526
- strlen($command),
527
- $command
528
- );
529
- if (!$this->_send_binary_packet($packet)) {
530
- return false;
531
- }
532
-
533
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
534
-
535
- $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
536
- if ($response === false) {
537
- return false;
538
- }
539
- }
540
-
541
- $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
542
-
543
- if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
544
- return false;
545
- }
546
-
547
- $response = $this->_get_sftp_packet();
548
- if ($this->packet_type != NET_SFTP_VERSION) {
549
- user_error('Expected SSH_FXP_VERSION');
550
- return false;
551
- }
552
-
553
- if (strlen($response) < 4) {
554
- return false;
555
- }
556
- extract(unpack('Nversion', $this->_string_shift($response, 4)));
557
- $this->version = $version;
558
- while (!empty($response)) {
559
- if (strlen($response) < 4) {
560
- return false;
561
- }
562
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
563
- $key = $this->_string_shift($response, $length);
564
- if (strlen($response) < 4) {
565
- return false;
566
- }
567
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
568
- $value = $this->_string_shift($response, $length);
569
- $this->extensions[$key] = $value;
570
- }
571
-
572
- /*
573
- SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
574
- however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
575
- not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
576
- one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
577
- 'newline@vandyke.com' would.
578
- */
579
- /*
580
- if (isset($this->extensions['newline@vandyke.com'])) {
581
- $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
582
- unset($this->extensions['newline@vandyke.com']);
583
- }
584
- */
585
-
586
- $this->use_request_id = true;
587
-
588
- /*
589
- A Note on SFTPv4/5/6 support:
590
- <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
591
-
592
- "If the client wishes to interoperate with servers that support noncontiguous version
593
- numbers it SHOULD send '3'"
594
-
595
- Given that the server only sends its version number after the client has already done so, the above
596
- seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
597
- most popular.
598
-
599
- <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
600
-
601
- "If the server did not send the "versions" extension, or the version-from-list was not included, the
602
- server MAY send a status response describing the failure, but MUST then close the channel without
603
- processing any further requests."
604
-
605
- So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
606
- a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
607
- v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
608
- in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
609
- channel and reopen it with a new and updated SSH_FXP_INIT packet.
610
- */
611
- switch ($this->version) {
612
- case 2:
613
- case 3:
614
- break;
615
- default:
616
- return false;
617
- }
618
-
619
- $this->pwd = $this->_realpath('.');
620
-
621
- $this->_update_stat_cache($this->pwd, array());
622
-
623
- return true;
624
- }
625
-
626
- /**
627
- * Disable the stat cache
628
- *
629
- * @access public
630
- */
631
- function disableStatCache()
632
- {
633
- $this->use_stat_cache = false;
634
- }
635
-
636
- /**
637
- * Enable the stat cache
638
- *
639
- * @access public
640
- */
641
- function enableStatCache()
642
- {
643
- $this->use_stat_cache = true;
644
- }
645
-
646
- /**
647
- * Clear the stat cache
648
- *
649
- * @access public
650
- */
651
- function clearStatCache()
652
- {
653
- $this->stat_cache = array();
654
- }
655
-
656
- /**
657
- * Enable path canonicalization
658
- *
659
- * @access public
660
- */
661
- function enablePathCanonicalization()
662
- {
663
- $this->canonicalize_paths = true;
664
- }
665
-
666
- /**
667
- * Enable path canonicalization
668
- *
669
- * @access public
670
- */
671
- function disablePathCanonicalization()
672
- {
673
- $this->canonicalize_paths = false;
674
- }
675
-
676
- /**
677
- * Returns the current directory name
678
- *
679
- * @return mixed
680
- * @access public
681
- */
682
- function pwd()
683
- {
684
- return $this->pwd;
685
- }
686
-
687
- /**
688
- * Logs errors
689
- *
690
- * @param string $response
691
- * @param int $status
692
- * @access public
693
- */
694
- function _logError($response, $status = -1)
695
- {
696
- if ($status == -1) {
697
- if (strlen($response) < 4) {
698
- return;
699
- }
700
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
701
- }
702
-
703
- $error = $this->status_codes[$status];
704
-
705
- if ($this->version > 2 || strlen($response) < 4) {
706
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
707
- $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
708
- } else {
709
- $this->sftp_errors[] = $error;
710
- }
711
- }
712
-
713
- /**
714
- * Returns canonicalized absolute pathname
715
- *
716
- * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
717
- * path and returns the canonicalized absolute pathname.
718
- *
719
- * @param string $path
720
- * @return mixed
721
- * @access public
722
- */
723
- function realpath($path)
724
- {
725
- return $this->_realpath($path);
726
- }
727
-
728
- /**
729
- * Canonicalize the Server-Side Path Name
730
- *
731
- * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
732
- * the absolute (canonicalized) path.
733
- *
734
- * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
735
- *
736
- * @see self::chdir()
737
- * @see self::disablePathCanonicalization()
738
- * @param string $path
739
- * @return mixed
740
- * @access private
741
- */
742
- function _realpath($path)
743
- {
744
- if (!$this->canonicalize_paths) {
745
- return $path;
746
- }
747
-
748
- if ($this->pwd === false) {
749
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
750
- if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
751
- return false;
752
- }
753
-
754
- $response = $this->_get_sftp_packet();
755
- switch ($this->packet_type) {
756
- case NET_SFTP_NAME:
757
- // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
758
- // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
759
- // at is the first part and that part is defined the same in SFTP versions 3 through 6.
760
- $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
761
- if (strlen($response) < 4) {
762
- return false;
763
- }
764
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
765
- return $this->_string_shift($response, $length);
766
- case NET_SFTP_STATUS:
767
- $this->_logError($response);
768
- return false;
769
- default:
770
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
771
- return false;
772
- }
773
- }
774
-
775
- if ($path[0] != '/') {
776
- $path = $this->pwd . '/' . $path;
777
- }
778
-
779
- $path = explode('/', $path);
780
- $new = array();
781
- foreach ($path as $dir) {
782
- if (!strlen($dir)) {
783
- continue;
784
- }
785
- switch ($dir) {
786
- case '..':
787
- array_pop($new);
788
- case '.':
789
- break;
790
- default:
791
- $new[] = $dir;
792
- }
793
- }
794
-
795
- return '/' . implode('/', $new);
796
- }
797
-
798
- /**
799
- * Changes the current directory
800
- *
801
- * @param string $dir
802
- * @return bool
803
- * @access public
804
- */
805
- function chdir($dir)
806
- {
807
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
808
- return false;
809
- }
810
-
811
- // assume current dir if $dir is empty
812
- if ($dir === '') {
813
- $dir = './';
814
- // suffix a slash if needed
815
- } elseif ($dir[strlen($dir) - 1] != '/') {
816
- $dir.= '/';
817
- }
818
-
819
- $dir = $this->_realpath($dir);
820
-
821
- // confirm that $dir is, in fact, a valid directory
822
- if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
823
- $this->pwd = $dir;
824
- return true;
825
- }
826
-
827
- // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
828
- // the currently logged in user has the appropriate permissions or not. maybe you could see if
829
- // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
830
- // way to get those with SFTP
831
-
832
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
833
- return false;
834
- }
835
-
836
- // see Net_SFTP::nlist() for a more thorough explanation of the following
837
- $response = $this->_get_sftp_packet();
838
- switch ($this->packet_type) {
839
- case NET_SFTP_HANDLE:
840
- $handle = substr($response, 4);
841
- break;
842
- case NET_SFTP_STATUS:
843
- $this->_logError($response);
844
- return false;
845
- default:
846
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
847
- return false;
848
- }
849
-
850
- if (!$this->_close_handle($handle)) {
851
- return false;
852
- }
853
-
854
- $this->_update_stat_cache($dir, array());
855
-
856
- $this->pwd = $dir;
857
- return true;
858
- }
859
-
860
- /**
861
- * Returns a list of files in the given directory
862
- *
863
- * @param string $dir
864
- * @param bool $recursive
865
- * @return mixed
866
- * @access public
867
- */
868
- function nlist($dir = '.', $recursive = false)
869
- {
870
- return $this->_nlist_helper($dir, $recursive, '');
871
- }
872
-
873
- /**
874
- * Helper method for nlist
875
- *
876
- * @param string $dir
877
- * @param bool $recursive
878
- * @param string $relativeDir
879
- * @return mixed
880
- * @access private
881
- */
882
- function _nlist_helper($dir, $recursive, $relativeDir)
883
- {
884
- $files = $this->_list($dir, false);
885
-
886
- if (!$recursive || $files === false) {
887
- return $files;
888
- }
889
-
890
- $result = array();
891
- foreach ($files as $value) {
892
- if ($value == '.' || $value == '..') {
893
- if ($relativeDir == '') {
894
- $result[] = $value;
895
- }
896
- continue;
897
- }
898
- if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
899
- $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
900
- $result = array_merge($result, $temp);
901
- } else {
902
- $result[] = $relativeDir . $value;
903
- }
904
- }
905
-
906
- return $result;
907
- }
908
-
909
- /**
910
- * Returns a detailed list of files in the given directory
911
- *
912
- * @param string $dir
913
- * @param bool $recursive
914
- * @return mixed
915
- * @access public
916
- */
917
- function rawlist($dir = '.', $recursive = false)
918
- {
919
- $files = $this->_list($dir, true);
920
- if (!$recursive || $files === false) {
921
- return $files;
922
- }
923
-
924
- static $depth = 0;
925
-
926
- foreach ($files as $key => $value) {
927
- if ($depth != 0 && $key == '..') {
928
- unset($files[$key]);
929
- continue;
930
- }
931
- $is_directory = false;
932
- if ($key != '.' && $key != '..') {
933
- if ($this->use_stat_cache) {
934
- $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)));
935
- } else {
936
- $stat = $this->lstat($dir . '/' . $key);
937
- $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
938
- }
939
- }
940
-
941
- if ($is_directory) {
942
- $depth++;
943
- $files[$key] = $this->rawlist($dir . '/' . $key, true);
944
- $depth--;
945
- } else {
946
- $files[$key] = (object) $value;
947
- }
948
- }
949
-
950
- return $files;
951
- }
952
-
953
- /**
954
- * Reads a list, be it detailed or not, of files in the given directory
955
- *
956
- * @param string $dir
957
- * @param bool $raw
958
- * @return mixed
959
- * @access private
960
- */
961
- function _list($dir, $raw = true)
962
- {
963
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
964
- return false;
965
- }
966
-
967
- $dir = $this->_realpath($dir . '/');
968
- if ($dir === false) {
969
- return false;
970
- }
971
-
972
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
973
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
974
- return false;
975
- }
976
-
977
- $response = $this->_get_sftp_packet();
978
- switch ($this->packet_type) {
979
- case NET_SFTP_HANDLE:
980
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
981
- // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
982
- // represent the length of the string and leave it at that
983
- $handle = substr($response, 4);
984
- break;
985
- case NET_SFTP_STATUS:
986
- // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
987
- $this->_logError($response);
988
- return false;
989
- default:
990
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
991
- return false;
992
- }
993
-
994
- $this->_update_stat_cache($dir, array());
995
-
996
- $contents = array();
997
- while (true) {
998
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
999
- // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
1000
- // SSH_MSG_CHANNEL_DATA messages is not known to me.
1001
- if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
1002
- return false;
1003
- }
1004
-
1005
- $response = $this->_get_sftp_packet();
1006
- switch ($this->packet_type) {
1007
- case NET_SFTP_NAME:
1008
- if (strlen($response) < 4) {
1009
- return false;
1010
- }
1011
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
1012
- for ($i = 0; $i < $count; $i++) {
1013
- if (strlen($response) < 4) {
1014
- return false;
1015
- }
1016
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1017
- $shortname = $this->_string_shift($response, $length);
1018
- if (strlen($response) < 4) {
1019
- return false;
1020
- }
1021
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1022
- $longname = $this->_string_shift($response, $length);
1023
- $attributes = $this->_parseAttributes($response);
1024
- if (!isset($attributes['type'])) {
1025
- $fileType = $this->_parseLongname($longname);
1026
- if ($fileType) {
1027
- $attributes['type'] = $fileType;
1028
- }
1029
- }
1030
- $contents[$shortname] = $attributes + array('filename' => $shortname);
1031
-
1032
- if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
1033
- $this->_update_stat_cache($dir . '/' . $shortname, array());
1034
- } else {
1035
- if ($shortname == '..') {
1036
- $temp = $this->_realpath($dir . '/..') . '/.';
1037
- } else {
1038
- $temp = $dir . '/' . $shortname;
1039
- }
1040
- $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
1041
- }
1042
- // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
1043
- // final SSH_FXP_STATUS packet should tell us that, already.
1044
- }
1045
- break;
1046
- case NET_SFTP_STATUS:
1047
- if (strlen($response) < 4) {
1048
- return false;
1049
- }
1050
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1051
- if ($status != NET_SFTP_STATUS_EOF) {
1052
- $this->_logError($response, $status);
1053
- return false;
1054
- }
1055
- break 2;
1056
- default:
1057
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1058
- return false;
1059
- }
1060
- }
1061
-
1062
- if (!$this->_close_handle($handle)) {
1063
- return false;
1064
- }
1065
-
1066
- if (count($this->sortOptions)) {
1067
- uasort($contents, array(&$this, '_comparator'));
1068
- }
1069
-
1070
- return $raw ? $contents : array_keys($contents);
1071
- }
1072
-
1073
- /**
1074
- * Compares two rawlist entries using parameters set by setListOrder()
1075
- *
1076
- * Intended for use with uasort()
1077
- *
1078
- * @param array $a
1079
- * @param array $b
1080
- * @return int
1081
- * @access private
1082
- */
1083
- function _comparator($a, $b)
1084
- {
1085
- switch (true) {
1086
- case $a['filename'] === '.' || $b['filename'] === '.':
1087
- if ($a['filename'] === $b['filename']) {
1088
- return 0;
1089
- }
1090
- return $a['filename'] === '.' ? -1 : 1;
1091
- case $a['filename'] === '..' || $b['filename'] === '..':
1092
- if ($a['filename'] === $b['filename']) {
1093
- return 0;
1094
- }
1095
- return $a['filename'] === '..' ? -1 : 1;
1096
- case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
1097
- if (!isset($b['type'])) {
1098
- return 1;
1099
- }
1100
- if ($b['type'] !== $a['type']) {
1101
- return -1;
1102
- }
1103
- break;
1104
- case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
1105
- return 1;
1106
- }
1107
- foreach ($this->sortOptions as $sort => $order) {
1108
- if (!isset($a[$sort]) || !isset($b[$sort])) {
1109
- if (isset($a[$sort])) {
1110
- return -1;
1111
- }
1112
- if (isset($b[$sort])) {
1113
- return 1;
1114
- }
1115
- return 0;
1116
- }
1117
- switch ($sort) {
1118
- case 'filename':
1119
- $result = strcasecmp($a['filename'], $b['filename']);
1120
- if ($result) {
1121
- return $order === SORT_DESC ? -$result : $result;
1122
- }
1123
- break;
1124
- case 'permissions':
1125
- case 'mode':
1126
- $a[$sort]&= 07777;
1127
- $b[$sort]&= 07777;
1128
- default:
1129
- if ($a[$sort] === $b[$sort]) {
1130
- break;
1131
- }
1132
- return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
1133
- }
1134
- }
1135
- }
1136
-
1137
- /**
1138
- * Defines how nlist() and rawlist() will be sorted - if at all.
1139
- *
1140
- * If sorting is enabled directories and files will be sorted independently with
1141
- * directories appearing before files in the resultant array that is returned.
1142
- *
1143
- * Any parameter returned by stat is a valid sort parameter for this function.
1144
- * Filename comparisons are case insensitive.
1145
- *
1146
- * Examples:
1147
- *
1148
- * $sftp->setListOrder('filename', SORT_ASC);
1149
- * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
1150
- * $sftp->setListOrder(true);
1151
- * Separates directories from files but doesn't do any sorting beyond that
1152
- * $sftp->setListOrder();
1153
- * Don't do any sort of sorting
1154
- *
1155
- * @access public
1156
- */
1157
- function setListOrder()
1158
- {
1159
- $this->sortOptions = array();
1160
- $args = func_get_args();
1161
- if (empty($args)) {
1162
- return;
1163
- }
1164
- $len = count($args) & 0x7FFFFFFE;
1165
- for ($i = 0; $i < $len; $i+=2) {
1166
- $this->sortOptions[$args[$i]] = $args[$i + 1];
1167
- }
1168
- if (!count($this->sortOptions)) {
1169
- $this->sortOptions = array('bogus' => true);
1170
- }
1171
- }
1172
-
1173
- /**
1174
- * Returns the file size, in bytes, or false, on failure
1175
- *
1176
- * Files larger than 4GB will show up as being exactly 4GB.
1177
- *
1178
- * @param string $filename
1179
- * @return mixed
1180
- * @access public
1181
- */
1182
- function size($filename)
1183
- {
1184
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1185
- return false;
1186
- }
1187
-
1188
- $result = $this->stat($filename);
1189
- if ($result === false) {
1190
- return false;
1191
- }
1192
- return isset($result['size']) ? $result['size'] : -1;
1193
- }
1194
-
1195
- /**
1196
- * Save files / directories to cache
1197
- *
1198
- * @param string $path
1199
- * @param mixed $value
1200
- * @access private
1201
- */
1202
- function _update_stat_cache($path, $value)
1203
- {
1204
- if ($this->use_stat_cache === false) {
1205
- return;
1206
- }
1207
-
1208
- // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
1209
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1210
-
1211
- $temp = &$this->stat_cache;
1212
- $max = count($dirs) - 1;
1213
- foreach ($dirs as $i => $dir) {
1214
- // if $temp is an object that means one of two things.
1215
- // 1. a file was deleted and changed to a directory behind phpseclib's back
1216
- // 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
1217
- if (is_object($temp)) {
1218
- $temp = array();
1219
- }
1220
- if (!isset($temp[$dir])) {
1221
- $temp[$dir] = array();
1222
- }
1223
- if ($i === $max) {
1224
- if (is_object($temp[$dir]) && is_object($value)) {
1225
- if (!isset($value->stat) && isset($temp[$dir]->stat)) {
1226
- $value->stat = $temp[$dir]->stat;
1227
- }
1228
- if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
1229
- $value->lstat = $temp[$dir]->lstat;
1230
- }
1231
- }
1232
- $temp[$dir] = $value;
1233
- break;
1234
- }
1235
- $temp = &$temp[$dir];
1236
- }
1237
- }
1238
-
1239
- /**
1240
- * Remove files / directories from cache
1241
- *
1242
- * @param string $path
1243
- * @return bool
1244
- * @access private
1245
- */
1246
- function _remove_from_stat_cache($path)
1247
- {
1248
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1249
-
1250
- $temp = &$this->stat_cache;
1251
- $max = count($dirs) - 1;
1252
- foreach ($dirs as $i => $dir) {
1253
- if ($i === $max) {
1254
- unset($temp[$dir]);
1255
- return true;
1256
- }
1257
- if (!isset($temp[$dir])) {
1258
- return false;
1259
- }
1260
- $temp = &$temp[$dir];
1261
- }
1262
- }
1263
-
1264
- /**
1265
- * Checks cache for path
1266
- *
1267
- * Mainly used by file_exists
1268
- *
1269
- * @param string $dir
1270
- * @return mixed
1271
- * @access private
1272
- */
1273
- function _query_stat_cache($path)
1274
- {
1275
- $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1276
-
1277
- $temp = &$this->stat_cache;
1278
- foreach ($dirs as $dir) {
1279
- if (!isset($temp[$dir])) {
1280
- return null;
1281
- }
1282
- $temp = &$temp[$dir];
1283
- }
1284
- return $temp;
1285
- }
1286
-
1287
- /**
1288
- * Returns general information about a file.
1289
- *
1290
- * Returns an array on success and false otherwise.
1291
- *
1292
- * @param string $filename
1293
- * @return mixed
1294
- * @access public
1295
- */
1296
- function stat($filename)
1297
- {
1298
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1299
- return false;
1300
- }
1301
-
1302
- $filename = $this->_realpath($filename);
1303
- if ($filename === false) {
1304
- return false;
1305
- }
1306
-
1307
- if ($this->use_stat_cache) {
1308
- $result = $this->_query_stat_cache($filename);
1309
- if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
1310
- return $result['.']->stat;
1311
- }
1312
- if (is_object($result) && isset($result->stat)) {
1313
- return $result->stat;
1314
- }
1315
- }
1316
-
1317
- $stat = $this->_stat($filename, NET_SFTP_STAT);
1318
- if ($stat === false) {
1319
- $this->_remove_from_stat_cache($filename);
1320
- return false;
1321
- }
1322
- if (isset($stat['type'])) {
1323
- if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1324
- $filename.= '/.';
1325
- }
1326
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1327
- return $stat;
1328
- }
1329
-
1330
- $pwd = $this->pwd;
1331
- $stat['type'] = $this->chdir($filename) ?
1332
- NET_SFTP_TYPE_DIRECTORY :
1333
- NET_SFTP_TYPE_REGULAR;
1334
- $this->pwd = $pwd;
1335
-
1336
- if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1337
- $filename.= '/.';
1338
- }
1339
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1340
-
1341
- return $stat;
1342
- }
1343
-
1344
- /**
1345
- * Returns general information about a file or symbolic link.
1346
- *
1347
- * Returns an array on success and false otherwise.
1348
- *
1349
- * @param string $filename
1350
- * @return mixed
1351
- * @access public
1352
- */
1353
- function lstat($filename)
1354
- {
1355
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1356
- return false;
1357
- }
1358
-
1359
- $filename = $this->_realpath($filename);
1360
- if ($filename === false) {
1361
- return false;
1362
- }
1363
-
1364
- if ($this->use_stat_cache) {
1365
- $result = $this->_query_stat_cache($filename);
1366
- if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
1367
- return $result['.']->lstat;
1368
- }
1369
- if (is_object($result) && isset($result->lstat)) {
1370
- return $result->lstat;
1371
- }
1372
- }
1373
-
1374
- $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
1375
- if ($lstat === false) {
1376
- $this->_remove_from_stat_cache($filename);
1377
- return false;
1378
- }
1379
- if (isset($lstat['type'])) {
1380
- if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1381
- $filename.= '/.';
1382
- }
1383
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1384
- return $lstat;
1385
- }
1386
-
1387
- $stat = $this->_stat($filename, NET_SFTP_STAT);
1388
-
1389
- if ($lstat != $stat) {
1390
- $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
1391
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1392
- return $stat;
1393
- }
1394
-
1395
- $pwd = $this->pwd;
1396
- $lstat['type'] = $this->chdir($filename) ?
1397
- NET_SFTP_TYPE_DIRECTORY :
1398
- NET_SFTP_TYPE_REGULAR;
1399
- $this->pwd = $pwd;
1400
-
1401
- if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1402
- $filename.= '/.';
1403
- }
1404
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1405
-
1406
- return $lstat;
1407
- }
1408
-
1409
- /**
1410
- * Returns general information about a file or symbolic link
1411
- *
1412
- * Determines information without calling Net_SFTP::realpath().
1413
- * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
1414
- *
1415
- * @param string $filename
1416
- * @param int $type
1417
- * @return mixed
1418
- * @access private
1419
- */
1420
- function _stat($filename, $type)
1421
- {
1422
- // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1423
- $packet = pack('Na*', strlen($filename), $filename);
1424
- if (!$this->_send_sftp_packet($type, $packet)) {
1425
- return false;
1426
- }
1427
-
1428
- $response = $this->_get_sftp_packet();
1429
- switch ($this->packet_type) {
1430
- case NET_SFTP_ATTRS:
1431
- return $this->_parseAttributes($response);
1432
- case NET_SFTP_STATUS:
1433
- $this->_logError($response);
1434
- return false;
1435
- }
1436
-
1437
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1438
- return false;
1439
- }
1440
-
1441
- /**
1442
- * Truncates a file to a given length
1443
- *
1444
- * @param string $filename
1445
- * @param int $new_size
1446
- * @return bool
1447
- * @access public
1448
- */
1449
- function truncate($filename, $new_size)
1450
- {
1451
- $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
1452
-
1453
- return $this->_setstat($filename, $attr, false);
1454
- }
1455
-
1456
- /**
1457
- * Sets access and modification time of file.
1458
- *
1459
- * If the file does not exist, it will be created.
1460
- *
1461
- * @param string $filename
1462
- * @param int $time
1463
- * @param int $atime
1464
- * @return bool
1465
- * @access public
1466
- */
1467
- function touch($filename, $time = null, $atime = null)
1468
- {
1469
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1470
- return false;
1471
- }
1472
-
1473
- $filename = $this->_realpath($filename);
1474
- if ($filename === false) {
1475
- return false;
1476
- }
1477
-
1478
- if (!isset($time)) {
1479
- $time = time();
1480
- }
1481
- if (!isset($atime)) {
1482
- $atime = $time;
1483
- }
1484
-
1485
- $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
1486
- $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
1487
- $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
1488
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1489
- return false;
1490
- }
1491
-
1492
- $response = $this->_get_sftp_packet();
1493
- switch ($this->packet_type) {
1494
- case NET_SFTP_HANDLE:
1495
- return $this->_close_handle(substr($response, 4));
1496
- case NET_SFTP_STATUS:
1497
- $this->_logError($response);
1498
- break;
1499
- default:
1500
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1501
- return false;
1502
- }
1503
-
1504
- return $this->_setstat($filename, $attr, false);
1505
- }
1506
-
1507
- /**
1508
- * Changes file or directory owner
1509
- *
1510
- * Returns true on success or false on error.
1511
- *
1512
- * @param string $filename
1513
- * @param int $uid
1514
- * @param bool $recursive
1515
- * @return bool
1516
- * @access public
1517
- */
1518
- function chown($filename, $uid, $recursive = false)
1519
- {
1520
- // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1521
- // "if the owner or group is specified as -1, then that ID is not changed"
1522
- $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
1523
-
1524
- return $this->_setstat($filename, $attr, $recursive);
1525
- }
1526
-
1527
- /**
1528
- * Changes file or directory group
1529
- *
1530
- * Returns true on success or false on error.
1531
- *
1532
- * @param string $filename
1533
- * @param int $gid
1534
- * @param bool $recursive
1535
- * @return bool
1536
- * @access public
1537
- */
1538
- function chgrp($filename, $gid, $recursive = false)
1539
- {
1540
- $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
1541
-
1542
- return $this->_setstat($filename, $attr, $recursive);
1543
- }
1544
-
1545
- /**
1546
- * Set permissions on a file.
1547
- *
1548
- * Returns the new file permissions on success or false on error.
1549
- * If $recursive is true than this just returns true or false.
1550
- *
1551
- * @param int $mode
1552
- * @param string $filename
1553
- * @param bool $recursive
1554
- * @return mixed
1555
- * @access public
1556
- */
1557
- function chmod($mode, $filename, $recursive = false)
1558
- {
1559
- if (is_string($mode) && is_int($filename)) {
1560
- $temp = $mode;
1561
- $mode = $filename;
1562
- $filename = $temp;
1563
- }
1564
-
1565
- $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1566
- if (!$this->_setstat($filename, $attr, $recursive)) {
1567
- return false;
1568
- }
1569
- if ($recursive) {
1570
- return true;
1571
- }
1572
-
1573
- $filename = $this->realpath($filename);
1574
- // rather than return what the permissions *should* be, we'll return what they actually are. this will also
1575
- // tell us if the file actually exists.
1576
- // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1577
- $packet = pack('Na*', strlen($filename), $filename);
1578
- if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
1579
- return false;
1580
- }
1581
-
1582
- $response = $this->_get_sftp_packet();
1583
- switch ($this->packet_type) {
1584
- case NET_SFTP_ATTRS:
1585
- $attrs = $this->_parseAttributes($response);
1586
- return $attrs['permissions'];
1587
- case NET_SFTP_STATUS:
1588
- $this->_logError($response);
1589
- return false;
1590
- }
1591
-
1592
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1593
- return false;
1594
- }
1595
-
1596
- /**
1597
- * Sets information about a file
1598
- *
1599
- * @param string $filename
1600
- * @param string $attr
1601
- * @param bool $recursive
1602
- * @return bool
1603
- * @access private
1604
- */
1605
- function _setstat($filename, $attr, $recursive)
1606
- {
1607
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1608
- return false;
1609
- }
1610
-
1611
- $filename = $this->_realpath($filename);
1612
- if ($filename === false) {
1613
- return false;
1614
- }
1615
-
1616
- $this->_remove_from_stat_cache($filename);
1617
-
1618
- if ($recursive) {
1619
- $i = 0;
1620
- $result = $this->_setstat_recursive($filename, $attr, $i);
1621
- $this->_read_put_responses($i);
1622
- return $result;
1623
- }
1624
-
1625
- // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1626
- // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1627
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
1628
- return false;
1629
- }
1630
-
1631
- /*
1632
- "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1633
- response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
1634
- servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1635
-
1636
- -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1637
- */
1638
- $response = $this->_get_sftp_packet();
1639
- if ($this->packet_type != NET_SFTP_STATUS) {
1640
- user_error('Expected SSH_FXP_STATUS');
1641
- return false;
1642
- }
1643
-
1644
- if (strlen($response) < 4) {
1645
- return false;
1646
- }
1647
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1648
- if ($status != NET_SFTP_STATUS_OK) {
1649
- $this->_logError($response, $status);
1650
- return false;
1651
- }
1652
-
1653
- return true;
1654
- }
1655
-
1656
- /**
1657
- * Recursively sets information on directories on the SFTP server
1658
- *
1659
- * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1660
- *
1661
- * @param string $path
1662
- * @param string $attr
1663
- * @param int $i
1664
- * @return bool
1665
- * @access private
1666
- */
1667
- function _setstat_recursive($path, $attr, &$i)
1668
- {
1669
- if (!$this->_read_put_responses($i)) {
1670
- return false;
1671
- }
1672
- $i = 0;
1673
- $entries = $this->_list($path, true);
1674
-
1675
- if ($entries === false) {
1676
- return $this->_setstat($path, $attr, false);
1677
- }
1678
-
1679
- // normally $entries would have at least . and .. but it might not if the directories
1680
- // permissions didn't allow reading
1681
- if (empty($entries)) {
1682
- return false;
1683
- }
1684
-
1685
- unset($entries['.'], $entries['..']);
1686
- foreach ($entries as $filename => $props) {
1687
- if (!isset($props['type'])) {
1688
- return false;
1689
- }
1690
-
1691
- $temp = $path . '/' . $filename;
1692
- if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1693
- if (!$this->_setstat_recursive($temp, $attr, $i)) {
1694
- return false;
1695
- }
1696
- } else {
1697
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
1698
- return false;
1699
- }
1700
-
1701
- $i++;
1702
-
1703
- if ($i >= NET_SFTP_QUEUE_SIZE) {
1704
- if (!$this->_read_put_responses($i)) {
1705
- return false;
1706
- }
1707
- $i = 0;
1708
- }
1709
- }
1710
- }
1711
-
1712
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
1713
- return false;
1714
- }
1715
-
1716
- $i++;
1717
-
1718
- if ($i >= NET_SFTP_QUEUE_SIZE) {
1719
- if (!$this->_read_put_responses($i)) {
1720
- return false;
1721
- }
1722
- $i = 0;
1723
- }
1724
-
1725
- return true;
1726
- }
1727
-
1728
- /**
1729
- * Return the target of a symbolic link
1730
- *
1731
- * @param string $link
1732
- * @return mixed
1733
- * @access public
1734
- */
1735
- function readlink($link)
1736
- {
1737
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1738
- return false;
1739
- }
1740
-
1741
- $link = $this->_realpath($link);
1742
-
1743
- if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
1744
- return false;
1745
- }
1746
-
1747
- $response = $this->_get_sftp_packet();
1748
- switch ($this->packet_type) {
1749
- case NET_SFTP_NAME:
1750
- break;
1751
- case NET_SFTP_STATUS:
1752
- $this->_logError($response);
1753
- return false;
1754
- default:
1755
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1756
- return false;
1757
- }
1758
-
1759
- if (strlen($response) < 4) {
1760
- return false;
1761
- }
1762
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
1763
- // the file isn't a symlink
1764
- if (!$count) {
1765
- return false;
1766
- }
1767
-
1768
- if (strlen($response) < 4) {
1769
- return false;
1770
- }
1771
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
1772
- return $this->_string_shift($response, $length);
1773
- }
1774
-
1775
- /**
1776
- * Create a symlink
1777
- *
1778
- * symlink() creates a symbolic link to the existing target with the specified name link.
1779
- *
1780
- * @param string $target
1781
- * @param string $link
1782
- * @return bool
1783
- * @access public
1784
- */
1785
- function symlink($target, $link)
1786
- {
1787
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1788
- return false;
1789
- }
1790
-
1791
- //$target = $this->_realpath($target);
1792
- $link = $this->_realpath($link);
1793
-
1794
- $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
1795
- if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
1796
- return false;
1797
- }
1798
-
1799
- $response = $this->_get_sftp_packet();
1800
- if ($this->packet_type != NET_SFTP_STATUS) {
1801
- user_error('Expected SSH_FXP_STATUS');
1802
- return false;
1803
- }
1804
-
1805
- if (strlen($response) < 4) {
1806
- return false;
1807
- }
1808
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1809
- if ($status != NET_SFTP_STATUS_OK) {
1810
- $this->_logError($response, $status);
1811
- return false;
1812
- }
1813
-
1814
- return true;
1815
- }
1816
-
1817
- /**
1818
- * Creates a directory.
1819
- *
1820
- * @param string $dir
1821
- * @return bool
1822
- * @access public
1823
- */
1824
- function mkdir($dir, $mode = -1, $recursive = false)
1825
- {
1826
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1827
- return false;
1828
- }
1829
-
1830
- $dir = $this->_realpath($dir);
1831
- // by not providing any permissions, hopefully the server will use the logged in users umask - their
1832
- // default permissions.
1833
- $attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1834
-
1835
- if ($recursive) {
1836
- $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1837
- if (empty($dirs[0])) {
1838
- array_shift($dirs);
1839
- $dirs[0] = '/' . $dirs[0];
1840
- }
1841
- for ($i = 0; $i < count($dirs); $i++) {
1842
- $temp = array_slice($dirs, 0, $i + 1);
1843
- $temp = implode('/', $temp);
1844
- $result = $this->_mkdir_helper($temp, $attr);
1845
- }
1846
- return $result;
1847
- }
1848
-
1849
- return $this->_mkdir_helper($dir, $attr);
1850
- }
1851
-
1852
- /**
1853
- * Helper function for directory creation
1854
- *
1855
- * @param string $dir
1856
- * @return bool
1857
- * @access private
1858
- */
1859
- function _mkdir_helper($dir, $attr)
1860
- {
1861
- if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
1862
- return false;
1863
- }
1864
-
1865
- $response = $this->_get_sftp_packet();
1866
- if ($this->packet_type != NET_SFTP_STATUS) {
1867
- user_error('Expected SSH_FXP_STATUS');
1868
- return false;
1869
- }
1870
-
1871
- if (strlen($response) < 4) {
1872
- return false;
1873
- }
1874
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1875
- if ($status != NET_SFTP_STATUS_OK) {
1876
- $this->_logError($response, $status);
1877
- return false;
1878
- }
1879
-
1880
- return true;
1881
- }
1882
-
1883
- /**
1884
- * Removes a directory.
1885
- *
1886
- * @param string $dir
1887
- * @return bool
1888
- * @access public
1889
- */
1890
- function rmdir($dir)
1891
- {
1892
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1893
- return false;
1894
- }
1895
-
1896
- $dir = $this->_realpath($dir);
1897
- if ($dir === false) {
1898
- return false;
1899
- }
1900
-
1901
- if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1902
- return false;
1903
- }
1904
-
1905
- $response = $this->_get_sftp_packet();
1906
- if ($this->packet_type != NET_SFTP_STATUS) {
1907
- user_error('Expected SSH_FXP_STATUS');
1908
- return false;
1909
- }
1910
-
1911
- if (strlen($response) < 4) {
1912
- return false;
1913
- }
1914
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1915
- if ($status != NET_SFTP_STATUS_OK) {
1916
- // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1917
- $this->_logError($response, $status);
1918
- return false;
1919
- }
1920
-
1921
- $this->_remove_from_stat_cache($dir);
1922
- // the following will do a soft delete, which would be useful if you deleted a file
1923
- // and then tried to do a stat on the deleted file. the above, in contrast, does
1924
- // a hard delete
1925
- //$this->_update_stat_cache($dir, false);
1926
-
1927
- return true;
1928
- }
1929
-
1930
- /**
1931
- * Uploads a file to the SFTP server.
1932
- *
1933
- * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
1934
- * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
1935
- * long, containing 'filename.ext' as its contents.
1936
- *
1937
- * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
1938
- * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
1939
- * large $remote_file will be, as well.
1940
- *
1941
- * If $data is a resource then it'll be used as a resource instead.
1942
- *
1943
- *
1944
- * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number
1945
- * of bytes to return, and returns a string if there is some data or null if there is no more data
1946
- *
1947
- * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
1948
- * care of that, yourself.
1949
- *
1950
- * $mode can take an additional two parameters - NET_SFTP_RESUME and NET_SFTP_RESUME_START. These are bitwise AND'd with
1951
- * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
1952
- *
1953
- * NET_SFTP_LOCAL_FILE | NET_SFTP_RESUME
1954
- *
1955
- * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
1956
- * NET_SFTP_RESUME with NET_SFTP_RESUME_START.
1957
- *
1958
- * If $mode & (NET_SFTP_RESUME | NET_SFTP_RESUME_START) then NET_SFTP_RESUME_START will be assumed.
1959
- *
1960
- * $start and $local_start give you more fine grained control over this process and take precident over NET_SFTP_RESUME
1961
- * when they're non-negative. ie. $start could let you write at the end of a file (like NET_SFTP_RESUME) or in the middle
1962
- * of one. $local_start could let you start your reading from the end of a file (like NET_SFTP_RESUME_START) or in the
1963
- * middle of one.
1964
- *
1965
- * Setting $local_start to > 0 or $mode | NET_SFTP_RESUME_START doesn't do anything unless $mode | NET_SFTP_LOCAL_FILE.
1966
- *
1967
- * @param string $remote_file
1968
- * @param string|resource $data
1969
- * @param int $mode
1970
- * @param int $start
1971
- * @param int $local_start
1972
- * @param callable|null $progressCallback
1973
- * @return bool
1974
- * @access public
1975
- * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
1976
- */
1977
- function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null)
1978
- {
1979
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1980
- return false;
1981
- }
1982
-
1983
- $remote_file = $this->_realpath($remote_file);
1984
- if ($remote_file === false) {
1985
- return false;
1986
- }
1987
-
1988
- $this->_remove_from_stat_cache($remote_file);
1989
-
1990
- $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
1991
- // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
1992
- // in practice, it doesn't seem to do that.
1993
- //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
1994
-
1995
- if ($start >= 0) {
1996
- $offset = $start;
1997
- } elseif ($mode & NET_SFTP_RESUME) {
1998
- // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
1999
- $size = $this->size($remote_file);
2000
- $offset = $size !== false ? $size : 0;
2001
- } else {
2002
- $offset = 0;
2003
- $flags|= NET_SFTP_OPEN_TRUNCATE;
2004
- }
2005
-
2006
- $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
2007
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2008
- return false;
2009
- }
2010
-
2011
- $response = $this->_get_sftp_packet();
2012
- switch ($this->packet_type) {
2013
- case NET_SFTP_HANDLE:
2014
- $handle = substr($response, 4);
2015
- break;
2016
- case NET_SFTP_STATUS:
2017
- $this->_logError($response);
2018
- return false;
2019
- default:
2020
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2021
- return false;
2022
- }
2023
-
2024
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
2025
- $dataCallback = false;
2026
- switch (true) {
2027
- case $mode & NET_SFTP_CALLBACK:
2028
- if (!is_callable($data)) {
2029
- user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag");
2030
- }
2031
- $dataCallback = $data;
2032
- // do nothing
2033
- break;
2034
- case is_resource($data):
2035
- $mode = $mode & ~NET_SFTP_LOCAL_FILE;
2036
- $info = stream_get_meta_data($data);
2037
- if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
2038
- $fp = fopen('php://memory', 'w+');
2039
- stream_copy_to_stream($data, $fp);
2040
- rewind($fp);
2041
- } else {
2042
- $fp = $data;
2043
- }
2044
- break;
2045
- case $mode & NET_SFTP_LOCAL_FILE:
2046
- if (!is_file($data)) {
2047
- user_error("$data is not a valid file");
2048
- return false;
2049
- }
2050
- $fp = @fopen($data, 'rb');
2051
- if (!$fp) {
2052
- return false;
2053
- }
2054
- }
2055
-
2056
- if (isset($fp)) {
2057
- $stat = fstat($fp);
2058
- $size = !empty($stat) ? $stat['size'] : 0;
2059
-
2060
- if ($local_start >= 0) {
2061
- fseek($fp, $local_start);
2062
- $size-= $local_start;
2063
- }
2064
- } elseif ($dataCallback) {
2065
- $size = 0;
2066
- } else {
2067
- $size = strlen($data);
2068
- }
2069
-
2070
- $sent = 0;
2071
- $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
2072
-
2073
- $sftp_packet_size = 4096; // PuTTY uses 4096
2074
- // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
2075
- $sftp_packet_size-= strlen($handle) + 25;
2076
- $i = 0;
2077
- while ($dataCallback || ($size === 0 || $sent < $size)) {
2078
- if ($dataCallback) {
2079
- $temp = call_user_func($dataCallback, $sftp_packet_size);
2080
- if (is_null($temp)) {
2081
- break;
2082
- }
2083
- } else {
2084
- $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
2085
- if ($temp === false || $temp === '') {
2086
- break;
2087
- }
2088
- }
2089
-
2090
- $subtemp = $offset + $sent;
2091
- $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
2092
- if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
2093
- if ($mode & NET_SFTP_LOCAL_FILE) {
2094
- fclose($fp);
2095
- }
2096
- return false;
2097
- }
2098
- $sent+= strlen($temp);
2099
- if (is_callable($progressCallback)) {
2100
- call_user_func($progressCallback, $sent);
2101
- }
2102
-
2103
- $i++;
2104
-
2105
- if ($i == NET_SFTP_QUEUE_SIZE) {
2106
- if (!$this->_read_put_responses($i)) {
2107
- $i = 0;
2108
- break;
2109
- }
2110
- $i = 0;
2111
- }
2112
- }
2113
-
2114
- if (!$this->_read_put_responses($i)) {
2115
- if ($mode & NET_SFTP_LOCAL_FILE) {
2116
- fclose($fp);
2117
- }
2118
- $this->_close_handle($handle);
2119
- return false;
2120
- }
2121
-
2122
- if ($mode & NET_SFTP_LOCAL_FILE) {
2123
- fclose($fp);
2124
- }
2125
-
2126
- return $this->_close_handle($handle);
2127
- }
2128
-
2129
- /**
2130
- * Reads multiple successive SSH_FXP_WRITE responses
2131
- *
2132
- * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
2133
- * SSH_FXP_WRITEs, in succession, and then reading $i responses.
2134
- *
2135
- * @param int $i
2136
- * @return bool
2137
- * @access private
2138
- */
2139
- function _read_put_responses($i)
2140
- {
2141
- while ($i--) {
2142
- $response = $this->_get_sftp_packet();
2143
- if ($this->packet_type != NET_SFTP_STATUS) {
2144
- user_error('Expected SSH_FXP_STATUS');
2145
- return false;
2146
- }
2147
-
2148
- if (strlen($response) < 4) {
2149
- return false;
2150
- }
2151
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2152
- if ($status != NET_SFTP_STATUS_OK) {
2153
- $this->_logError($response, $status);
2154
- break;
2155
- }
2156
- }
2157
-
2158
- return $i < 0;
2159
- }
2160
-
2161
- /**
2162
- * Close handle
2163
- *
2164
- * @param string $handle
2165
- * @return bool
2166
- * @access private
2167
- */
2168
- function _close_handle($handle)
2169
- {
2170
- if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
2171
- return false;
2172
- }
2173
-
2174
- // "The client MUST release all resources associated with the handle regardless of the status."
2175
- // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
2176
- $response = $this->_get_sftp_packet();
2177
- if ($this->packet_type != NET_SFTP_STATUS) {
2178
- user_error('Expected SSH_FXP_STATUS');
2179
- return false;
2180
- }
2181
-
2182
- if (strlen($response) < 4) {
2183
- return false;
2184
- }
2185
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2186
- if ($status != NET_SFTP_STATUS_OK) {
2187
- $this->_logError($response, $status);
2188
- return false;
2189
- }
2190
-
2191
- return true;
2192
- }
2193
-
2194
- /**
2195
- * Downloads a file from the SFTP server.
2196
- *
2197
- * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
2198
- * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
2199
- * operation.
2200
- *
2201
- * $offset and $length can be used to download files in chunks.
2202
- *
2203
- * @param string $remote_file
2204
- * @param string $local_file
2205
- * @param int $offset
2206
- * @param int $length
2207
- * @return mixed
2208
- * @access public
2209
- */
2210
- function get($remote_file, $local_file = false, $offset = 0, $length = -1)
2211
- {
2212
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2213
- return false;
2214
- }
2215
-
2216
- $remote_file = $this->_realpath($remote_file);
2217
- if ($remote_file === false) {
2218
- return false;
2219
- }
2220
-
2221
- $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
2222
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2223
- return false;
2224
- }
2225
-
2226
- $response = $this->_get_sftp_packet();
2227
- switch ($this->packet_type) {
2228
- case NET_SFTP_HANDLE:
2229
- $handle = substr($response, 4);
2230
- break;
2231
- case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2232
- $this->_logError($response);
2233
- return false;
2234
- default:
2235
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2236
- return false;
2237
- }
2238
-
2239
- if (is_resource($local_file)) {
2240
- $fp = $local_file;
2241
- $stat = fstat($fp);
2242
- $res_offset = $stat['size'];
2243
- } else {
2244
- $res_offset = 0;
2245
- if ($local_file !== false) {
2246
- $fp = fopen($local_file, 'wb');
2247
- if (!$fp) {
2248
- return false;
2249
- }
2250
- } else {
2251
- $content = '';
2252
- }
2253
- }
2254
-
2255
- $fclose_check = $local_file !== false && !is_resource($local_file);
2256
-
2257
- $start = $offset;
2258
- $read = 0;
2259
- while (true) {
2260
- $i = 0;
2261
-
2262
- while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
2263
- $tempoffset = $start + $read;
2264
-
2265
- $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
2266
-
2267
- $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
2268
- if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
2269
- if ($fclose_check) {
2270
- fclose($fp);
2271
- }
2272
- return false;
2273
- }
2274
- $packet = null;
2275
- $read+= $packet_size;
2276
- $i++;
2277
- }
2278
-
2279
- if (!$i) {
2280
- break;
2281
- }
2282
-
2283
- $packets_sent = $i - 1;
2284
-
2285
- $clear_responses = false;
2286
- while ($i > 0) {
2287
- $i--;
2288
-
2289
- if ($clear_responses) {
2290
- $this->_get_sftp_packet($packets_sent - $i);
2291
- continue;
2292
- } else {
2293
- $response = $this->_get_sftp_packet($packets_sent - $i);
2294
- }
2295
-
2296
- switch ($this->packet_type) {
2297
- case NET_SFTP_DATA:
2298
- $temp = substr($response, 4);
2299
- $offset+= strlen($temp);
2300
- if ($local_file === false) {
2301
- $content.= $temp;
2302
- } else {
2303
- fputs($fp, $temp);
2304
- }
2305
- $temp = null;
2306
- break;
2307
- case NET_SFTP_STATUS:
2308
- // could, in theory, return false if !strlen($content) but we'll hold off for the time being
2309
- $this->_logError($response);
2310
- $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
2311
- break;
2312
- default:
2313
- if ($fclose_check) {
2314
- fclose($fp);
2315
- }
2316
- user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
2317
- }
2318
- $response = null;
2319
- }
2320
-
2321
- if ($clear_responses) {
2322
- break;
2323
- }
2324
- }
2325
-
2326
- if ($length > 0 && $length <= $offset - $start) {
2327
- if ($local_file === false) {
2328
- $content = substr($content, 0, $length);
2329
- } else {
2330
- ftruncate($fp, $length + $res_offset);
2331
- }
2332
- }
2333
-
2334
- if ($fclose_check) {
2335
- fclose($fp);
2336
- }
2337
-
2338
- if (!$this->_close_handle($handle)) {
2339
- return false;
2340
- }
2341
-
2342
- // if $content isn't set that means a file was written to
2343
- return isset($content) ? $content : true;
2344
- }
2345
-
2346
- /**
2347
- * Deletes a file on the SFTP server.
2348
- *
2349
- * @param string $path
2350
- * @param bool $recursive
2351
- * @return bool
2352
- * @access public
2353
- */
2354
- function delete($path, $recursive = true)
2355
- {
2356
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2357
- return false;
2358
- }
2359
-
2360
- if (is_object($path)) {
2361
- // It's an object. Cast it as string before we check anything else.
2362
- $path = (string) $path;
2363
- }
2364
-
2365
- if (!is_string($path) || $path == '') {
2366
- return false;
2367
- }
2368
-
2369
- $path = $this->_realpath($path);
2370
- if ($path === false) {
2371
- return false;
2372
- }
2373
-
2374
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2375
- if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
2376
- return false;
2377
- }
2378
-
2379
- $response = $this->_get_sftp_packet();
2380
- if ($this->packet_type != NET_SFTP_STATUS) {
2381
- user_error('Expected SSH_FXP_STATUS');
2382
- return false;
2383
- }
2384
-
2385
- // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2386
- if (strlen($response) < 4) {
2387
- return false;
2388
- }
2389
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2390
- if ($status != NET_SFTP_STATUS_OK) {
2391
- $this->_logError($response, $status);
2392
- if (!$recursive) {
2393
- return false;
2394
- }
2395
- $i = 0;
2396
- $result = $this->_delete_recursive($path, $i);
2397
- $this->_read_put_responses($i);
2398
- return $result;
2399
- }
2400
-
2401
- $this->_remove_from_stat_cache($path);
2402
-
2403
- return true;
2404
- }
2405
-
2406
- /**
2407
- * Recursively deletes directories on the SFTP server
2408
- *
2409
- * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
2410
- *
2411
- * @param string $path
2412
- * @param int $i
2413
- * @return bool
2414
- * @access private
2415
- */
2416
- function _delete_recursive($path, &$i)
2417
- {
2418
- if (!$this->_read_put_responses($i)) {
2419
- return false;
2420
- }
2421
- $i = 0;
2422
- $entries = $this->_list($path, true);
2423
-
2424
- // normally $entries would have at least . and .. but it might not if the directories
2425
- // permissions didn't allow reading
2426
- if (empty($entries)) {
2427
- return false;
2428
- }
2429
-
2430
- unset($entries['.'], $entries['..']);
2431
- foreach ($entries as $filename => $props) {
2432
- if (!isset($props['type'])) {
2433
- return false;
2434
- }
2435
-
2436
- $temp = $path . '/' . $filename;
2437
- if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
2438
- if (!$this->_delete_recursive($temp, $i)) {
2439
- return false;
2440
- }
2441
- } else {
2442
- if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
2443
- return false;
2444
- }
2445
- $this->_remove_from_stat_cache($temp);
2446
-
2447
- $i++;
2448
-
2449
- if ($i >= NET_SFTP_QUEUE_SIZE) {
2450
- if (!$this->_read_put_responses($i)) {
2451
- return false;
2452
- }
2453
- $i = 0;
2454
- }
2455
- }
2456
- }
2457
-
2458
- if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
2459
- return false;
2460
- }
2461
- $this->_remove_from_stat_cache($path);
2462
-
2463
- $i++;
2464
-
2465
- if ($i >= NET_SFTP_QUEUE_SIZE) {
2466
- if (!$this->_read_put_responses($i)) {
2467
- return false;
2468
- }
2469
- $i = 0;
2470
- }
2471
-
2472
- return true;
2473
- }
2474
-
2475
- /**
2476
- * Checks whether a file or directory exists
2477
- *
2478
- * @param string $path
2479
- * @return bool
2480
- * @access public
2481
- */
2482
- function file_exists($path)
2483
- {
2484
- if ($this->use_stat_cache) {
2485
- $path = $this->_realpath($path);
2486
-
2487
- $result = $this->_query_stat_cache($path);
2488
-
2489
- if (isset($result)) {
2490
- // return true if $result is an array or if it's an stdClass object
2491
- return $result !== false;
2492
- }
2493
- }
2494
-
2495
- return $this->stat($path) !== false;
2496
- }
2497
-
2498
- /**
2499
- * Tells whether the filename is a directory
2500
- *
2501
- * @param string $path
2502
- * @return bool
2503
- * @access public
2504
- */
2505
- function is_dir($path)
2506
- {
2507
- $result = $this->_get_stat_cache_prop($path, 'type');
2508
- if ($result === false) {
2509
- return false;
2510
- }
2511
- return $result === NET_SFTP_TYPE_DIRECTORY;
2512
- }
2513
-
2514
- /**
2515
- * Tells whether the filename is a regular file
2516
- *
2517
- * @param string $path
2518
- * @return bool
2519
- * @access public
2520
- */
2521
- function is_file($path)
2522
- {
2523
- $result = $this->_get_stat_cache_prop($path, 'type');
2524
- if ($result === false) {
2525
- return false;
2526
- }
2527
- return $result === NET_SFTP_TYPE_REGULAR;
2528
- }
2529
-
2530
- /**
2531
- * Tells whether the filename is a symbolic link
2532
- *
2533
- * @param string $path
2534
- * @return bool
2535
- * @access public
2536
- */
2537
- function is_link($path)
2538
- {
2539
- $result = $this->_get_lstat_cache_prop($path, 'type');
2540
- if ($result === false) {
2541
- return false;
2542
- }
2543
- return $result === NET_SFTP_TYPE_SYMLINK;
2544
- }
2545
-
2546
- /**
2547
- * Tells whether a file exists and is readable
2548
- *
2549
- * @param string $path
2550
- * @return bool
2551
- * @access public
2552
- */
2553
- function is_readable($path)
2554
- {
2555
- $path = $this->_realpath($path);
2556
-
2557
- $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
2558
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2559
- return false;
2560
- }
2561
-
2562
- $response = $this->_get_sftp_packet();
2563
- switch ($this->packet_type) {
2564
- case NET_SFTP_HANDLE:
2565
- return true;
2566
- case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2567
- return false;
2568
- default:
2569
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2570
- return false;
2571
- }
2572
- }
2573
-
2574
- /**
2575
- * Tells whether the filename is writable
2576
- *
2577
- * @param string $path
2578
- * @return bool
2579
- * @access public
2580
- */
2581
- function is_writable($path)
2582
- {
2583
- $path = $this->_realpath($path);
2584
-
2585
- $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
2586
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2587
- return false;
2588
- }
2589
-
2590
- $response = $this->_get_sftp_packet();
2591
- switch ($this->packet_type) {
2592
- case NET_SFTP_HANDLE:
2593
- return true;
2594
- case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2595
- return false;
2596
- default:
2597
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2598
- return false;
2599
- }
2600
- }
2601
-
2602
- /**
2603
- * Tells whether the filename is writeable
2604
- *
2605
- * Alias of is_writable
2606
- *
2607
- * @param string $path
2608
- * @return bool
2609
- * @access public
2610
- */
2611
- function is_writeable($path)
2612
- {
2613
- return $this->is_writable($path);
2614
- }
2615
-
2616
- /**
2617
- * Gets last access time of file
2618
- *
2619
- * @param string $path
2620
- * @return mixed
2621
- * @access public
2622
- */
2623
- function fileatime($path)
2624
- {
2625
- return $this->_get_stat_cache_prop($path, 'atime');
2626
- }
2627
-
2628
- /**
2629
- * Gets file modification time
2630
- *
2631
- * @param string $path
2632
- * @return mixed
2633
- * @access public
2634
- */
2635
- function filemtime($path)
2636
- {
2637
- return $this->_get_stat_cache_prop($path, 'mtime');
2638
- }
2639
-
2640
- /**
2641
- * Gets file permissions
2642
- *
2643
- * @param string $path
2644
- * @return mixed
2645
- * @access public
2646
- */
2647
- function fileperms($path)
2648
- {
2649
- return $this->_get_stat_cache_prop($path, 'permissions');
2650
- }
2651
-
2652
- /**
2653
- * Gets file owner
2654
- *
2655
- * @param string $path
2656
- * @return mixed
2657
- * @access public
2658
- */
2659
- function fileowner($path)
2660
- {
2661
- return $this->_get_stat_cache_prop($path, 'uid');
2662
- }
2663
-
2664
- /**
2665
- * Gets file group
2666
- *
2667
- * @param string $path
2668
- * @return mixed
2669
- * @access public
2670
- */
2671
- function filegroup($path)
2672
- {
2673
- return $this->_get_stat_cache_prop($path, 'gid');
2674
- }
2675
-
2676
- /**
2677
- * Gets file size
2678
- *
2679
- * @param string $path
2680
- * @return mixed
2681
- * @access public
2682
- */
2683
- function filesize($path)
2684
- {
2685
- return $this->_get_stat_cache_prop($path, 'size');
2686
- }
2687
-
2688
- /**
2689
- * Gets file type
2690
- *
2691
- * @param string $path
2692
- * @return mixed
2693
- * @access public
2694
- */
2695
- function filetype($path)
2696
- {
2697
- $type = $this->_get_stat_cache_prop($path, 'type');
2698
- if ($type === false) {
2699
- return false;
2700
- }
2701
-
2702
- switch ($type) {
2703
- case NET_SFTP_TYPE_BLOCK_DEVICE:
2704
- return 'block';
2705
- case NET_SFTP_TYPE_CHAR_DEVICE:
2706
- return 'char';
2707
- case NET_SFTP_TYPE_DIRECTORY:
2708
- return 'dir';
2709
- case NET_SFTP_TYPE_FIFO:
2710
- return 'fifo';
2711
- case NET_SFTP_TYPE_REGULAR:
2712
- return 'file';
2713
- case NET_SFTP_TYPE_SYMLINK:
2714
- return 'link';
2715
- default:
2716
- return false;
2717
- }
2718
- }
2719
-
2720
- /**
2721
- * Return a stat properity
2722
- *
2723
- * Uses cache if appropriate.
2724
- *
2725
- * @param string $path
2726
- * @param string $prop
2727
- * @return mixed
2728
- * @access private
2729
- */
2730
- function _get_stat_cache_prop($path, $prop)
2731
- {
2732
- return $this->_get_xstat_cache_prop($path, $prop, 'stat');
2733
- }
2734
-
2735
- /**
2736
- * Return an lstat properity
2737
- *
2738
- * Uses cache if appropriate.
2739
- *
2740
- * @param string $path
2741
- * @param string $prop
2742
- * @return mixed
2743
- * @access private
2744
- */
2745
- function _get_lstat_cache_prop($path, $prop)
2746
- {
2747
- return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
2748
- }
2749
-
2750
- /**
2751
- * Return a stat or lstat properity
2752
- *
2753
- * Uses cache if appropriate.
2754
- *
2755
- * @param string $path
2756
- * @param string $prop
2757
- * @return mixed
2758
- * @access private
2759
- */
2760
- function _get_xstat_cache_prop($path, $prop, $type)
2761
- {
2762
- if ($this->use_stat_cache) {
2763
- $path = $this->_realpath($path);
2764
-
2765
- $result = $this->_query_stat_cache($path);
2766
-
2767
- if (is_object($result) && isset($result->$type)) {
2768
- return $result->{$type}[$prop];
2769
- }
2770
- }
2771
-
2772
- $result = $this->$type($path);
2773
-
2774
- if ($result === false || !isset($result[$prop])) {
2775
- return false;
2776
- }
2777
-
2778
- return $result[$prop];
2779
- }
2780
-
2781
- /**
2782
- * Renames a file or a directory on the SFTP server
2783
- *
2784
- * @param string $oldname
2785
- * @param string $newname
2786
- * @return bool
2787
- * @access public
2788
- */
2789
- function rename($oldname, $newname)
2790
- {
2791
- if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2792
- return false;
2793
- }
2794
-
2795
- $oldname = $this->_realpath($oldname);
2796
- $newname = $this->_realpath($newname);
2797
- if ($oldname === false || $newname === false) {
2798
- return false;
2799
- }
2800
-
2801
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2802
- $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
2803
- if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
2804
- return false;
2805
- }
2806
-
2807
- $response = $this->_get_sftp_packet();
2808
- if ($this->packet_type != NET_SFTP_STATUS) {
2809
- user_error('Expected SSH_FXP_STATUS');
2810
- return false;
2811
- }
2812
-
2813
- // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2814
- if (strlen($response) < 4) {
2815
- return false;
2816
- }
2817
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2818
- if ($status != NET_SFTP_STATUS_OK) {
2819
- $this->_logError($response, $status);
2820
- return false;
2821
- }
2822
-
2823
- // don't move the stat cache entry over since this operation could very well change the
2824
- // atime and mtime attributes
2825
- //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
2826
- $this->_remove_from_stat_cache($oldname);
2827
- $this->_remove_from_stat_cache($newname);
2828
-
2829
- return true;
2830
- }
2831
-
2832
- /**
2833
- * Parse Attributes
2834
- *
2835
- * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
2836
- *
2837
- * @param string $response
2838
- * @return array
2839
- * @access private
2840
- */
2841
- function _parseAttributes(&$response)
2842
- {
2843
- $attr = array();
2844
- if (strlen($response) < 4) {
2845
- user_error('Malformed file attributes');
2846
- return array();
2847
- }
2848
- extract(unpack('Nflags', $this->_string_shift($response, 4)));
2849
- // SFTPv4+ have a type field (a byte) that follows the above flag field
2850
- foreach ($this->attributes as $key => $value) {
2851
- switch ($flags & $key) {
2852
- case NET_SFTP_ATTR_SIZE: // 0x00000001
2853
- // The size attribute is defined as an unsigned 64-bit integer.
2854
- // The following will use floats on 32-bit platforms, if necessary.
2855
- // As can be seen in the BigInteger class, floats are generally
2856
- // IEEE 754 binary64 "double precision" on such platforms and
2857
- // as such can represent integers of at least 2^50 without loss
2858
- // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
2859
- $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
2860
- break;
2861
- case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
2862
- if (strlen($response) < 8) {
2863
- user_error('Malformed file attributes');
2864
- return $attr;
2865
- }
2866
- $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
2867
- break;
2868
- case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
2869
- if (strlen($response) < 4) {
2870
- user_error('Malformed file attributes');
2871
- return $attr;
2872
- }
2873
- $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
2874
- // mode == permissions; permissions was the original array key and is retained for bc purposes.
2875
- // mode was added because that's the more industry standard terminology
2876
- $attr+= array('mode' => $attr['permissions']);
2877
- $fileType = $this->_parseMode($attr['permissions']);
2878
- if ($fileType !== false) {
2879
- $attr+= array('type' => $fileType);
2880
- }
2881
- break;
2882
- case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
2883
- if (strlen($response) < 8) {
2884
- user_error('Malformed file attributes');
2885
- return $attr;
2886
- }
2887
- $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
2888
- break;
2889
- case NET_SFTP_ATTR_EXTENDED: // 0x80000000
2890
- if (strlen($response) < 4) {
2891
- user_error('Malformed file attributes');
2892
- return $attr;
2893
- }
2894
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
2895
- for ($i = 0; $i < $count; $i++) {
2896
- if (strlen($response) < 4) {
2897
- user_error('Malformed file attributes');
2898
- return $attr;
2899
- }
2900
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
2901
- $key = $this->_string_shift($response, $length);
2902
- if (strlen($response) < 4) {
2903
- user_error('Malformed file attributes');
2904
- return $attr;
2905
- }
2906
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
2907
- $attr[$key] = $this->_string_shift($response, $length);
2908
- }
2909
- }
2910
- }
2911
- return $attr;
2912
- }
2913
-
2914
- /**
2915
- * Attempt to identify the file type
2916
- *
2917
- * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
2918
- *
2919
- * @param int $mode
2920
- * @return int
2921
- * @access private
2922
- */
2923
- function _parseMode($mode)
2924
- {
2925
- // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
2926
- // see, also, http://linux.die.net/man/2/stat
2927
- switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
2928
- case 0000000: // no file type specified - figure out the file type using alternative means
2929
- return false;
2930
- case 0040000:
2931
- return NET_SFTP_TYPE_DIRECTORY;
2932
- case 0100000:
2933
- return NET_SFTP_TYPE_REGULAR;
2934
- case 0120000:
2935
- return NET_SFTP_TYPE_SYMLINK;
2936
- // new types introduced in SFTPv5+
2937
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
2938
- case 0010000: // named pipe (fifo)
2939
- return NET_SFTP_TYPE_FIFO;
2940
- case 0020000: // character special
2941
- return NET_SFTP_TYPE_CHAR_DEVICE;
2942
- case 0060000: // block special
2943
- return NET_SFTP_TYPE_BLOCK_DEVICE;
2944
- case 0140000: // socket
2945
- return NET_SFTP_TYPE_SOCKET;
2946
- case 0160000: // whiteout
2947
- // "SPECIAL should be used for files that are of
2948
- // a known type which cannot be expressed in the protocol"
2949
- return NET_SFTP_TYPE_SPECIAL;
2950
- default:
2951
- return NET_SFTP_TYPE_UNKNOWN;
2952
- }
2953
- }
2954
-
2955
- /**
2956
- * Parse Longname
2957
- *
2958
- * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
2959
- * a file as a directory and see if an error is returned or you could try to parse the
2960
- * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
2961
- * The result is returned using the
2962
- * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
2963
- *
2964
- * If the longname is in an unrecognized format bool(false) is returned.
2965
- *
2966
- * @param string $longname
2967
- * @return mixed
2968
- * @access private
2969
- */
2970
- function _parseLongname($longname)
2971
- {
2972
- // http://en.wikipedia.org/wiki/Unix_file_types
2973
- // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
2974
- if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
2975
- switch ($longname[0]) {
2976
- case '-':
2977
- return NET_SFTP_TYPE_REGULAR;
2978
- case 'd':
2979
- return NET_SFTP_TYPE_DIRECTORY;
2980
- case 'l':
2981
- return NET_SFTP_TYPE_SYMLINK;
2982
- default:
2983
- return NET_SFTP_TYPE_SPECIAL;
2984
- }
2985
- }
2986
-
2987
- return false;
2988
- }
2989
-
2990
- /**
2991
- * Sends SFTP Packets
2992
- *
2993
- * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
2994
- *
2995
- * @param int $type
2996
- * @param string $data
2997
- * @see self::_get_sftp_packet()
2998
- * @see Net_SSH2::_send_channel_packet()
2999
- * @return bool
3000
- * @access private
3001
- */
3002
- function _send_sftp_packet($type, $data, $request_id = 1)
3003
- {
3004
- $packet = $this->use_request_id ?
3005
- pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
3006
- pack('NCa*', strlen($data) + 1, $type, $data);
3007
-
3008
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
3009
- $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
3010
- $stop = strtok(microtime(), ' ') + strtok('');
3011
-
3012
- if (defined('NET_SFTP_LOGGING')) {
3013
- $packet_type = '-> ' . $this->packet_types[$type] .
3014
- ' (' . round($stop - $start, 4) . 's)';
3015
- if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
3016
- echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
3017
- flush();
3018
- ob_flush();
3019
- } else {
3020
- $this->packet_type_log[] = $packet_type;
3021
- if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
3022
- $this->packet_log[] = $data;
3023
- }
3024
- }
3025
- }
3026
-
3027
- return $result;
3028
- }
3029
-
3030
- /**
3031
- * Receives SFTP Packets
3032
- *
3033
- * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3034
- *
3035
- * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
3036
- * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
3037
- * messages containing one SFTP packet.
3038
- *
3039
- * @see self::_send_sftp_packet()
3040
- * @return string
3041
- * @access private
3042
- */
3043
- function _get_sftp_packet($request_id = null)
3044
- {
3045
- if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
3046
- $this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
3047
- $temp = $this->requestBuffer[$request_id]['packet'];
3048
- unset($this->requestBuffer[$request_id]);
3049
- return $temp;
3050
- }
3051
-
3052
- // in SSH2.php the timeout is cumulative per function call. eg. exec() will
3053
- // timeout after 10s. but for SFTP.php it's cumulative per packet
3054
- $this->curTimeout = $this->timeout;
3055
-
3056
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
3057
-
3058
- // SFTP packet length
3059
- while (strlen($this->packet_buffer) < 4) {
3060
- $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
3061
- if (is_bool($temp)) {
3062
- $this->packet_type = false;
3063
- $this->packet_buffer = '';
3064
- return false;
3065
- }
3066
- $this->packet_buffer.= $temp;
3067
- }
3068
- if (strlen($this->packet_buffer) < 4) {
3069
- return false;
3070
- }
3071
- extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
3072
- $tempLength = $length;
3073
- $tempLength-= strlen($this->packet_buffer);
3074
-
3075
-
3076
- // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
3077
- if ($tempLength > 256 * 1024) {
3078
- user_error('Invalid SFTP packet size');
3079
- return false;
3080
- }
3081
-
3082
- // SFTP packet type and data payload
3083
- while ($tempLength > 0) {
3084
- $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
3085
- if (is_bool($temp)) {
3086
- $this->packet_type = false;
3087
- $this->packet_buffer = '';
3088
- return false;
3089
- }
3090
- $this->packet_buffer.= $temp;
3091
- $tempLength-= strlen($temp);
3092
- }
3093
-
3094
- $stop = strtok(microtime(), ' ') + strtok('');
3095
-
3096
- $this->packet_type = ord($this->_string_shift($this->packet_buffer));
3097
-
3098
- if ($this->use_request_id) {
3099
- extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id
3100
- $length-= 5; // account for the request id and the packet type
3101
- } else {
3102
- $length-= 1; // account for the packet type
3103
- }
3104
-
3105
- $packet = $this->_string_shift($this->packet_buffer, $length);
3106
-
3107
- if (defined('NET_SFTP_LOGGING')) {
3108
- $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
3109
- ' (' . round($stop - $start, 4) . 's)';
3110
- if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
3111
- echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
3112
- flush();
3113
- ob_flush();
3114
- } else {
3115
- $this->packet_type_log[] = $packet_type;
3116
- if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
3117
- $this->packet_log[] = $packet;
3118
- }
3119
- }
3120
- }
3121
-
3122
- if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
3123
- $this->requestBuffer[$packet_id] = array(
3124
- 'packet_type' => $this->packet_type,
3125
- 'packet' => $packet
3126
- );
3127
- return $this->_get_sftp_packet($request_id);
3128
- }
3129
-
3130
- return $packet;
3131
- }
3132
-
3133
- /**
3134
- * Returns a log of the packets that have been sent and received.
3135
- *
3136
- * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
3137
- *
3138
- * @access public
3139
- * @return string or Array
3140
- */
3141
- function getSFTPLog()
3142
- {
3143
- if (!defined('NET_SFTP_LOGGING')) {
3144
- return false;
3145
- }
3146
-
3147
- switch (NET_SFTP_LOGGING) {
3148
- case NET_SFTP_LOG_COMPLEX:
3149
- return $this->_format_log($this->packet_log, $this->packet_type_log);
3150
- break;
3151
- //case NET_SFTP_LOG_SIMPLE:
3152
- default:
3153
- return $this->packet_type_log;
3154
- }
3155
- }
3156
-
3157
- /**
3158
- * Returns all errors
3159
- *
3160
- * @return array
3161
- * @access public
3162
- */
3163
- function getSFTPErrors()
3164
- {
3165
- return $this->sftp_errors;
3166
- }
3167
-
3168
- /**
3169
- * Returns the last error
3170
- *
3171
- * @return string
3172
- * @access public
3173
- */
3174
- function getLastSFTPError()
3175
- {
3176
- return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
3177
- }
3178
-
3179
- /**
3180
- * Get supported SFTP versions
3181
- *
3182
- * @return array
3183
- * @access public
3184
- */
3185
- function getSupportedVersions()
3186
- {
3187
- $temp = array('version' => $this->version);
3188
- if (isset($this->extensions['versions'])) {
3189
- $temp['extensions'] = $this->extensions['versions'];
3190
- }
3191
- return $temp;
3192
- }
3193
-
3194
- /**
3195
- * Disconnect
3196
- *
3197
- * @param int $reason
3198
- * @return bool
3199
- * @access private
3200
- */
3201
- function _disconnect($reason)
3202
- {
3203
- $this->pwd = false;
3204
- parent::_disconnect($reason);
3205
- }
3206
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Pure-PHP implementation of SFTP.
5
+ *
6
+ * PHP versions 4 and 5
7
+ *
8
+ * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
9
+ * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
10
+ * to an SFTPv4/5/6 server.
11
+ *
12
+ * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
13
+ *
14
+ * Here's a short example of how to use this library:
15
+ * <code>
16
+ * <?php
17
+ * include 'Net/SFTP.php';
18
+ *
19
+ * $sftp = new Net_SFTP('www.domain.tld');
20
+ * if (!$sftp->login('username', 'password')) {
21
+ * exit('Login Failed');
22
+ * }
23
+ *
24
+ * echo $sftp->pwd() . "\r\n";
25
+ * $sftp->put('filename.ext', 'hello, world!');
26
+ * print_r($sftp->nlist());
27
+ * ?>
28
+ * </code>
29
+ *
30
+ * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
31
+ * of this software and associated documentation files (the "Software"), to deal
32
+ * in the Software without restriction, including without limitation the rights
33
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
+ * copies of the Software, and to permit persons to whom the Software is
35
+ * furnished to do so, subject to the following conditions:
36
+ *
37
+ * The above copyright notice and this permission notice shall be included in
38
+ * all copies or substantial portions of the Software.
39
+ *
40
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
+ * THE SOFTWARE.
47
+ *
48
+ * @category Net
49
+ * @package Net_SFTP
50
+ * @author Jim Wigginton <terrafrost@php.net>
51
+ * @copyright 2009 Jim Wigginton
52
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
53
+ * @link http://phpseclib.sourceforge.net
54
+ */
55
+
56
+ /**
57
+ * Include Net_SSH2
58
+ */
59
+ if (!class_exists('Net_SSH2')) {
60
+ include_once 'SSH2.php';
61
+ }
62
+
63
+ /**#@+
64
+ * @access public
65
+ * @see self::getLog()
66
+ */
67
+ /**
68
+ * Returns the message numbers
69
+ */
70
+ define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
71
+ /**
72
+ * Returns the message content
73
+ */
74
+ define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
75
+ /**
76
+ * Outputs the message content in real-time.
77
+ */
78
+ define('NET_SFTP_LOG_REALTIME', 3);
79
+ /**#@-*/
80
+
81
+ /**
82
+ * SFTP channel constant
83
+ *
84
+ * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
85
+ *
86
+ * @see Net_SSH2::_send_channel_packet()
87
+ * @see Net_SSH2::_get_channel_packet()
88
+ * @access private
89
+ */
90
+ define('NET_SFTP_CHANNEL', 0x100);
91
+
92
+ /**#@+
93
+ * @access public
94
+ * @see self::put()
95
+ */
96
+ /**
97
+ * Reads data from a local file.
98
+ */
99
+ define('NET_SFTP_LOCAL_FILE', 1);
100
+ /**
101
+ * Reads data from a string.
102
+ */
103
+ // this value isn't really used anymore but i'm keeping it reserved for historical reasons
104
+ define('NET_SFTP_STRING', 2);
105
+ /**
106
+ * Reads data from callback:
107
+ * function callback($length) returns string to proceed, null for EOF
108
+ */
109
+ define('NET_SFTP_CALLBACK', 16);
110
+ /**
111
+ * Resumes an upload
112
+ */
113
+ define('NET_SFTP_RESUME', 4);
114
+ /**
115
+ * Append a local file to an already existing remote file
116
+ */
117
+ define('NET_SFTP_RESUME_START', 8);
118
+ /**#@-*/
119
+
120
+ /**
121
+ * Pure-PHP implementations of SFTP.
122
+ *
123
+ * @package Net_SFTP
124
+ * @author Jim Wigginton <terrafrost@php.net>
125
+ * @access public
126
+ */
127
+ class Net_SFTP extends Net_SSH2
128
+ {
129
+ /**
130
+ * Packet Types
131
+ *
132
+ * @see self::Net_SFTP()
133
+ * @var array
134
+ * @access private
135
+ */
136
+ var $packet_types = array();
137
+
138
+ /**
139
+ * Status Codes
140
+ *
141
+ * @see self::Net_SFTP()
142
+ * @var array
143
+ * @access private
144
+ */
145
+ var $status_codes = array();
146
+
147
+ /**
148
+ * The Request ID
149
+ *
150
+ * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
151
+ * concurrent actions, so it's somewhat academic, here.
152
+ *
153
+ * @var boolean
154
+ * @see self::_send_sftp_packet()
155
+ * @access private
156
+ */
157
+ var $use_request_id = false;
158
+
159
+ /**
160
+ * The Packet Type
161
+ *
162
+ * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
163
+ * concurrent actions, so it's somewhat academic, here.
164
+ *
165
+ * @var int
166
+ * @see self::_get_sftp_packet()
167
+ * @access private
168
+ */
169
+ var $packet_type = -1;
170
+
171
+ /**
172
+ * Packet Buffer
173
+ *
174
+ * @var string
175
+ * @see self::_get_sftp_packet()
176
+ * @access private
177
+ */
178
+ var $packet_buffer = '';
179
+
180
+ /**
181
+ * Extensions supported by the server
182
+ *
183
+ * @var array
184
+ * @see self::_initChannel()
185
+ * @access private
186
+ */
187
+ var $extensions = array();
188
+
189
+ /**
190
+ * Server SFTP version
191
+ *
192
+ * @var int
193
+ * @see self::_initChannel()
194
+ * @access private
195
+ */
196
+ var $version;
197
+
198
+ /**
199
+ * Current working directory
200
+ *
201
+ * @var string
202
+ * @see self::realpath()
203
+ * @see self::chdir()
204
+ * @access private
205
+ */
206
+ var $pwd = false;
207
+
208
+ /**
209
+ * Packet Type Log
210
+ *
211
+ * @see self::getLog()
212
+ * @var array
213
+ * @access private
214
+ */
215
+ var $packet_type_log = array();
216
+
217
+ /**
218
+ * Packet Log
219
+ *
220
+ * @see self::getLog()
221
+ * @var array
222
+ * @access private
223
+ */
224
+ var $packet_log = array();
225
+
226
+ /**
227
+ * Error information
228
+ *
229
+ * @see self::getSFTPErrors()
230
+ * @see self::getLastSFTPError()
231
+ * @var array
232
+ * @access private
233
+ */
234
+ var $sftp_errors = array();
235
+
236
+ /**
237
+ * Stat Cache
238
+ *
239
+ * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
240
+ * we'll cache the results.
241
+ *
242
+ * @see self::_update_stat_cache()
243
+ * @see self::_remove_from_stat_cache()
244
+ * @see self::_query_stat_cache()
245
+ * @var array
246
+ * @access private
247
+ */
248
+ var $stat_cache = array();
249
+
250
+ /**
251
+ * Max SFTP Packet Size
252
+ *
253
+ * @see self::Net_SFTP()
254
+ * @see self::get()
255
+ * @var array
256
+ * @access private
257
+ */
258
+ var $max_sftp_packet;
259
+
260
+ /**
261
+ * Stat Cache Flag
262
+ *
263
+ * @see self::disableStatCache()
264
+ * @see self::enableStatCache()
265
+ * @var bool
266
+ * @access private
267
+ */
268
+ var $use_stat_cache = true;
269
+
270
+ /**
271
+ * Sort Options
272
+ *
273
+ * @see self::_comparator()
274
+ * @see self::setListOrder()
275
+ * @var array
276
+ * @access private
277
+ */
278
+ var $sortOptions = array();
279
+
280
+ /**
281
+ * Canonicalization Flag
282
+ *
283
+ * Determines whether or not paths should be canonicalized before being
284
+ * passed on to the remote server.
285
+ *
286
+ * @see self::enablePathCanonicalization()
287
+ * @see self::disablePathCanonicalization()
288
+ * @see self::realpath()
289
+ * @var bool
290
+ * @access private
291
+ */
292
+ var $canonicalize_paths = true;
293
+
294
+ /**
295
+ * Request Buffers
296
+ *
297
+ * @see self::_get_sftp_packet()
298
+ * @var array
299
+ * @access private
300
+ */
301
+ var $requestBuffer = array();
302
+
303
+ /**
304
+ * Default Constructor.
305
+ *
306
+ * Connects to an SFTP server
307
+ *
308
+ * @param string $host
309
+ * @param int $port
310
+ * @param int $timeout
311
+ * @return Net_SFTP
312
+ * @access public
313
+ */
314
+ function __construct($host, $port = 22, $timeout = 10)
315
+ {
316
+ parent::__construct($host, $port, $timeout);
317
+
318
+ $this->max_sftp_packet = 1 << 15;
319
+
320
+ $this->packet_types = array(
321
+ 1 => 'NET_SFTP_INIT',
322
+ 2 => 'NET_SFTP_VERSION',
323
+ /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
324
+ SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
325
+ pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
326
+ 3 => 'NET_SFTP_OPEN',
327
+ 4 => 'NET_SFTP_CLOSE',
328
+ 5 => 'NET_SFTP_READ',
329
+ 6 => 'NET_SFTP_WRITE',
330
+ 7 => 'NET_SFTP_LSTAT',
331
+ 9 => 'NET_SFTP_SETSTAT',
332
+ 11 => 'NET_SFTP_OPENDIR',
333
+ 12 => 'NET_SFTP_READDIR',
334
+ 13 => 'NET_SFTP_REMOVE',
335
+ 14 => 'NET_SFTP_MKDIR',
336
+ 15 => 'NET_SFTP_RMDIR',
337
+ 16 => 'NET_SFTP_REALPATH',
338
+ 17 => 'NET_SFTP_STAT',
339
+ /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
340
+ SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
341
+ pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
342
+ 18 => 'NET_SFTP_RENAME',
343
+ 19 => 'NET_SFTP_READLINK',
344
+ 20 => 'NET_SFTP_SYMLINK',
345
+
346
+ 101=> 'NET_SFTP_STATUS',
347
+ 102=> 'NET_SFTP_HANDLE',
348
+ /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
349
+ SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
350
+ pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
351
+ 103=> 'NET_SFTP_DATA',
352
+ 104=> 'NET_SFTP_NAME',
353
+ 105=> 'NET_SFTP_ATTRS',
354
+
355
+ 200=> 'NET_SFTP_EXTENDED'
356
+ );
357
+ $this->status_codes = array(
358
+ 0 => 'NET_SFTP_STATUS_OK',
359
+ 1 => 'NET_SFTP_STATUS_EOF',
360
+ 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
361
+ 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
362
+ 4 => 'NET_SFTP_STATUS_FAILURE',
363
+ 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
364
+ 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
365
+ 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
366
+ 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
367
+ 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
368
+ 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
369
+ 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
370
+ 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
371
+ 13 => 'NET_SFTP_STATUS_NO_MEDIA',
372
+ 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
373
+ 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
374
+ 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
375
+ 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
376
+ 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
377
+ 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
378
+ 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
379
+ 21 => 'NET_SFTP_STATUS_LINK_LOOP',
380
+ 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
381
+ 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
382
+ 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
383
+ 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
384
+ 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
385
+ 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
386
+ 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
387
+ 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
388
+ 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
389
+ 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
390
+ );
391
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
392
+ // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
393
+ $this->attributes = array(
394
+ 0x00000001 => 'NET_SFTP_ATTR_SIZE',
395
+ 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
396
+ 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
397
+ 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
398
+ // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
399
+ // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
400
+ // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
401
+ // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
402
+ (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
403
+ );
404
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
405
+ // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
406
+ // the array for that $this->open5_flags and similarly alter the constant names.
407
+ $this->open_flags = array(
408
+ 0x00000001 => 'NET_SFTP_OPEN_READ',
409
+ 0x00000002 => 'NET_SFTP_OPEN_WRITE',
410
+ 0x00000004 => 'NET_SFTP_OPEN_APPEND',
411
+ 0x00000008 => 'NET_SFTP_OPEN_CREATE',
412
+ 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
413
+ 0x00000020 => 'NET_SFTP_OPEN_EXCL'
414
+ );
415
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
416
+ // see Net_SFTP::_parseLongname() for an explanation
417
+ $this->file_types = array(
418
+ 1 => 'NET_SFTP_TYPE_REGULAR',
419
+ 2 => 'NET_SFTP_TYPE_DIRECTORY',
420
+ 3 => 'NET_SFTP_TYPE_SYMLINK',
421
+ 4 => 'NET_SFTP_TYPE_SPECIAL',
422
+ 5 => 'NET_SFTP_TYPE_UNKNOWN',
423
+ // the followin types were first defined for use in SFTPv5+
424
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
425
+ 6 => 'NET_SFTP_TYPE_SOCKET',
426
+ 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
427
+ 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
428
+ 9 => 'NET_SFTP_TYPE_FIFO'
429
+ );
430
+ $this->_define_array(
431
+ $this->packet_types,
432
+ $this->status_codes,
433
+ $this->attributes,
434
+ $this->open_flags,
435
+ $this->file_types
436
+ );
437
+
438
+ if (!defined('NET_SFTP_QUEUE_SIZE')) {
439
+ define('NET_SFTP_QUEUE_SIZE', 32);
440
+ }
441
+ if (!defined('NET_SFTP_UPLOAD_QUEUE_SIZE')) {
442
+ define('NET_SFTP_UPLOAD_QUEUE_SIZE', 1024);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * PHP4 compatible Default Constructor.
448
+ *
449
+ * @see self::__construct()
450
+ * @param string $host
451
+ * @param int $port
452
+ * @param int $timeout
453
+ * @access public
454
+ */
455
+ function Net_SFTP($host, $port = 22, $timeout = 10)
456
+ {
457
+ $this->__construct($host, $port, $timeout);
458
+ }
459
+
460
+ /**
461
+ * Login
462
+ *
463
+ * @param string $username
464
+ * @param string $password
465
+ * @return bool
466
+ * @access public
467
+ */
468
+ function login($username)
469
+ {
470
+ $args = func_get_args();
471
+ $callback = version_compare(PHP_VERSION, '5.3.0') < 0 ?
472
+ array(&$this, 'parent::login') :
473
+ 'parent::login';
474
+ if (!call_user_func_array($callback, $args)) {
475
+ return false;
476
+ }
477
+
478
+ $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
479
+
480
+ $packet = pack(
481
+ 'CNa*N3',
482
+ NET_SSH2_MSG_CHANNEL_OPEN,
483
+ strlen('session'),
484
+ 'session',
485
+ NET_SFTP_CHANNEL,
486
+ $this->window_size,
487
+ 0x4000
488
+ );
489
+
490
+ if (!$this->_send_binary_packet($packet)) {
491
+ return false;
492
+ }
493
+
494
+ $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
495
+
496
+ $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
497
+ if ($response === false) {
498
+ return false;
499
+ }
500
+
501
+ $packet = pack(
502
+ 'CNNa*CNa*',
503
+ NET_SSH2_MSG_CHANNEL_REQUEST,
504
+ $this->server_channels[NET_SFTP_CHANNEL],
505
+ strlen('subsystem'),
506
+ 'subsystem',
507
+ 1,
508
+ strlen('sftp'),
509
+ 'sftp'
510
+ );
511
+ if (!$this->_send_binary_packet($packet)) {
512
+ return false;
513
+ }
514
+
515
+ $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
516
+
517
+ $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
518
+ if ($response === false) {
519
+ // from PuTTY's psftp.exe
520
+ $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
521
+ "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
522
+ "exec sftp-server";
523
+ // 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
524
+ // is redundant
525
+ $packet = pack(
526
+ 'CNNa*CNa*',
527
+ NET_SSH2_MSG_CHANNEL_REQUEST,
528
+ $this->server_channels[NET_SFTP_CHANNEL],
529
+ strlen('exec'),
530
+ 'exec',
531
+ 1,
532
+ strlen($command),
533
+ $command
534
+ );
535
+ if (!$this->_send_binary_packet($packet)) {
536
+ return false;
537
+ }
538
+
539
+ $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
540
+
541
+ $response = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
542
+ if ($response === false) {
543
+ return false;
544
+ }
545
+ }
546
+
547
+ $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
548
+
549
+ if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
550
+ return false;
551
+ }
552
+
553
+ $response = $this->_get_sftp_packet();
554
+ if ($this->packet_type != NET_SFTP_VERSION) {
555
+ user_error('Expected SSH_FXP_VERSION');
556
+ return false;
557
+ }
558
+
559
+ if (strlen($response) < 4) {
560
+ return false;
561
+ }
562
+ extract(unpack('Nversion', $this->_string_shift($response, 4)));
563
+ $this->version = $version;
564
+ while (!empty($response)) {
565
+ if (strlen($response) < 4) {
566
+ return false;
567
+ }
568
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
569
+ $key = $this->_string_shift($response, $length);
570
+ if (strlen($response) < 4) {
571
+ return false;
572
+ }
573
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
574
+ $value = $this->_string_shift($response, $length);
575
+ $this->extensions[$key] = $value;
576
+ }
577
+
578
+ /*
579
+ SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
580
+ however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
581
+ not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
582
+ one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
583
+ 'newline@vandyke.com' would.
584
+ */
585
+ /*
586
+ if (isset($this->extensions['newline@vandyke.com'])) {
587
+ $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
588
+ unset($this->extensions['newline@vandyke.com']);
589
+ }
590
+ */
591
+
592
+ $this->use_request_id = true;
593
+
594
+ /*
595
+ A Note on SFTPv4/5/6 support:
596
+ <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
597
+
598
+ "If the client wishes to interoperate with servers that support noncontiguous version
599
+ numbers it SHOULD send '3'"
600
+
601
+ Given that the server only sends its version number after the client has already done so, the above
602
+ seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
603
+ most popular.
604
+
605
+ <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
606
+
607
+ "If the server did not send the "versions" extension, or the version-from-list was not included, the
608
+ server MAY send a status response describing the failure, but MUST then close the channel without
609
+ processing any further requests."
610
+
611
+ So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
612
+ a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
613
+ v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
614
+ in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
615
+ channel and reopen it with a new and updated SSH_FXP_INIT packet.
616
+ */
617
+ switch ($this->version) {
618
+ case 2:
619
+ case 3:
620
+ break;
621
+ default:
622
+ return false;
623
+ }
624
+
625
+ $this->pwd = $this->_realpath('.');
626
+
627
+ $this->_update_stat_cache($this->pwd, array());
628
+
629
+ return true;
630
+ }
631
+
632
+ /**
633
+ * Disable the stat cache
634
+ *
635
+ * @access public
636
+ */
637
+ function disableStatCache()
638
+ {
639
+ $this->use_stat_cache = false;
640
+ }
641
+
642
+ /**
643
+ * Enable the stat cache
644
+ *
645
+ * @access public
646
+ */
647
+ function enableStatCache()
648
+ {
649
+ $this->use_stat_cache = true;
650
+ }
651
+
652
+ /**
653
+ * Clear the stat cache
654
+ *
655
+ * @access public
656
+ */
657
+ function clearStatCache()
658
+ {
659
+ $this->stat_cache = array();
660
+ }
661
+
662
+ /**
663
+ * Enable path canonicalization
664
+ *
665
+ * @access public
666
+ */
667
+ function enablePathCanonicalization()
668
+ {
669
+ $this->canonicalize_paths = true;
670
+ }
671
+
672
+ /**
673
+ * Enable path canonicalization
674
+ *
675
+ * @access public
676
+ */
677
+ function disablePathCanonicalization()
678
+ {
679
+ $this->canonicalize_paths = false;
680
+ }
681
+
682
+ /**
683
+ * Returns the current directory name
684
+ *
685
+ * @return mixed
686
+ * @access public
687
+ */
688
+ function pwd()
689
+ {
690
+ return $this->pwd;
691
+ }
692
+
693
+ /**
694
+ * Logs errors
695
+ *
696
+ * @param string $response
697
+ * @param int $status
698
+ * @access public
699
+ */
700
+ function _logError($response, $status = -1)
701
+ {
702
+ if ($status == -1) {
703
+ if (strlen($response) < 4) {
704
+ return;
705
+ }
706
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
707
+ }
708
+
709
+ $error = $this->status_codes[$status];
710
+
711
+ if ($this->version > 2 || strlen($response) < 4) {
712
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
713
+ $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
714
+ } else {
715
+ $this->sftp_errors[] = $error;
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Returns canonicalized absolute pathname
721
+ *
722
+ * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
723
+ * path and returns the canonicalized absolute pathname.
724
+ *
725
+ * @param string $path
726
+ * @return mixed
727
+ * @access public
728
+ */
729
+ function realpath($path)
730
+ {
731
+ return $this->_realpath($path);
732
+ }
733
+
734
+ /**
735
+ * Canonicalize the Server-Side Path Name
736
+ *
737
+ * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
738
+ * the absolute (canonicalized) path.
739
+ *
740
+ * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
741
+ *
742
+ * @see self::chdir()
743
+ * @see self::disablePathCanonicalization()
744
+ * @param string $path
745
+ * @return mixed
746
+ * @access private
747
+ */
748
+ function _realpath($path)
749
+ {
750
+ if (!$this->canonicalize_paths) {
751
+ return $path;
752
+ }
753
+
754
+ if ($this->pwd === false) {
755
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
756
+ if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
757
+ return false;
758
+ }
759
+
760
+ $response = $this->_get_sftp_packet();
761
+ switch ($this->packet_type) {
762
+ case NET_SFTP_NAME:
763
+ // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
764
+ // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
765
+ // at is the first part and that part is defined the same in SFTP versions 3 through 6.
766
+ $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
767
+ if (strlen($response) < 4) {
768
+ return false;
769
+ }
770
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
771
+ return $this->_string_shift($response, $length);
772
+ case NET_SFTP_STATUS:
773
+ $this->_logError($response);
774
+ return false;
775
+ default:
776
+ user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
777
+ return false;
778
+ }
779
+ }
780
+
781
+ if (!strlen($path) || $path[0] != '/') {
782
+ $path = $this->pwd . '/' . $path;
783
+ }
784
+
785
+ $path = explode('/', $path);
786
+ $new = array();
787
+ foreach ($path as $dir) {
788
+ if (!strlen($dir)) {
789
+ continue;
790
+ }
791
+ switch ($dir) {
792
+ case '..':
793
+ array_pop($new);
794
+ case '.':
795
+ break;
796
+ default:
797
+ $new[] = $dir;
798
+ }
799
+ }
800
+
801
+ return '/' . implode('/', $new);
802
+ }
803
+
804
+ /**
805
+ * Changes the current directory
806
+ *
807
+ * @param string $dir
808
+ * @return bool
809
+ * @access public
810
+ */
811
+ function chdir($dir)
812
+ {
813
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
814
+ return false;
815
+ }
816
+
817
+ // assume current dir if $dir is empty
818
+ if ($dir === '') {
819
+ $dir = './';
820
+ // suffix a slash if needed
821
+ } elseif ($dir[strlen($dir) - 1] != '/') {
822
+ $dir.= '/';
823
+ }
824
+
825
+ $dir = $this->_realpath($dir);
826
+
827
+ // confirm that $dir is, in fact, a valid directory
828
+ if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
829
+ $this->pwd = $dir;
830
+ return true;
831
+ }
832
+
833
+ // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
834
+ // the currently logged in user has the appropriate permissions or not. maybe you could see if
835
+ // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
836
+ // way to get those with SFTP
837
+
838
+ if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
839
+ return false;
840
+ }
841
+
842
+ // see Net_SFTP::nlist() for a more thorough explanation of the following
843
+ $response = $this->_get_sftp_packet();
844
+ switch ($this->packet_type) {
845
+ case NET_SFTP_HANDLE:
846
+ $handle = substr($response, 4);
847
+ break;
848
+ case NET_SFTP_STATUS:
849
+ $this->_logError($response);
850
+ return false;
851
+ default:
852
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
853
+ return false;
854
+ }
855
+
856
+ if (!$this->_close_handle($handle)) {
857
+ return false;
858
+ }
859
+
860
+ $this->_update_stat_cache($dir, array());
861
+
862
+ $this->pwd = $dir;
863
+ return true;
864
+ }
865
+
866
+ /**
867
+ * Returns a list of files in the given directory
868
+ *
869
+ * @param string $dir
870
+ * @param bool $recursive
871
+ * @return mixed
872
+ * @access public
873
+ */
874
+ function nlist($dir = '.', $recursive = false)
875
+ {
876
+ return $this->_nlist_helper($dir, $recursive, '');
877
+ }
878
+
879
+ /**
880
+ * Helper method for nlist
881
+ *
882
+ * @param string $dir
883
+ * @param bool $recursive
884
+ * @param string $relativeDir
885
+ * @return mixed
886
+ * @access private
887
+ */
888
+ function _nlist_helper($dir, $recursive, $relativeDir)
889
+ {
890
+ $files = $this->_list($dir, false);
891
+
892
+ if (!$recursive || $files === false) {
893
+ return $files;
894
+ }
895
+
896
+ $result = array();
897
+ foreach ($files as $value) {
898
+ if ($value == '.' || $value == '..') {
899
+ if ($relativeDir == '') {
900
+ $result[] = $value;
901
+ }
902
+ continue;
903
+ }
904
+ if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
905
+ $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
906
+ $temp = is_array($temp) ? $temp : array();
907
+ $result = array_merge($result, $temp);
908
+ } else {
909
+ $result[] = $relativeDir . $value;
910
+ }
911
+ }
912
+
913
+ return $result;
914
+ }
915
+
916
+ /**
917
+ * Returns a detailed list of files in the given directory
918
+ *
919
+ * @param string $dir
920
+ * @param bool $recursive
921
+ * @return mixed
922
+ * @access public
923
+ */
924
+ function rawlist($dir = '.', $recursive = false)
925
+ {
926
+ $files = $this->_list($dir, true);
927
+ if (!$recursive || $files === false) {
928
+ return $files;
929
+ }
930
+
931
+ static $depth = 0;
932
+
933
+ foreach ($files as $key => $value) {
934
+ if ($depth != 0 && $key == '..') {
935
+ unset($files[$key]);
936
+ continue;
937
+ }
938
+ $is_directory = false;
939
+ if ($key != '.' && $key != '..') {
940
+ if ($this->use_stat_cache) {
941
+ $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)));
942
+ } else {
943
+ $stat = $this->lstat($dir . '/' . $key);
944
+ $is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
945
+ }
946
+ }
947
+
948
+ if ($is_directory) {
949
+ $depth++;
950
+ $files[$key] = $this->rawlist($dir . '/' . $key, true);
951
+ $depth--;
952
+ } else {
953
+ $files[$key] = (object) $value;
954
+ }
955
+ }
956
+
957
+ return $files;
958
+ }
959
+
960
+ /**
961
+ * Reads a list, be it detailed or not, of files in the given directory
962
+ *
963
+ * @param string $dir
964
+ * @param bool $raw
965
+ * @return mixed
966
+ * @access private
967
+ */
968
+ function _list($dir, $raw = true)
969
+ {
970
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
971
+ return false;
972
+ }
973
+
974
+ $dir = $this->_realpath($dir . '/');
975
+ if ($dir === false) {
976
+ return false;
977
+ }
978
+
979
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
980
+ if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
981
+ return false;
982
+ }
983
+
984
+ $response = $this->_get_sftp_packet();
985
+ switch ($this->packet_type) {
986
+ case NET_SFTP_HANDLE:
987
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
988
+ // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
989
+ // represent the length of the string and leave it at that
990
+ $handle = substr($response, 4);
991
+ break;
992
+ case NET_SFTP_STATUS:
993
+ // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
994
+ $this->_logError($response);
995
+ return false;
996
+ default:
997
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
998
+ return false;
999
+ }
1000
+
1001
+ $this->_update_stat_cache($dir, array());
1002
+
1003
+ $contents = array();
1004
+ while (true) {
1005
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
1006
+ // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
1007
+ // SSH_MSG_CHANNEL_DATA messages is not known to me.
1008
+ if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
1009
+ return false;
1010
+ }
1011
+
1012
+ $response = $this->_get_sftp_packet();
1013
+ switch ($this->packet_type) {
1014
+ case NET_SFTP_NAME:
1015
+ if (strlen($response) < 4) {
1016
+ return false;
1017
+ }
1018
+ extract(unpack('Ncount', $this->_string_shift($response, 4)));
1019
+ for ($i = 0; $i < $count; $i++) {
1020
+ if (strlen($response) < 4) {
1021
+ return false;
1022
+ }
1023
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
1024
+ $shortname = $this->_string_shift($response, $length);
1025
+ if (strlen($response) < 4) {
1026
+ return false;
1027
+ }
1028
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
1029
+ $longname = $this->_string_shift($response, $length);
1030
+ $attributes = $this->_parseAttributes($response);
1031
+ if (!isset($attributes['type'])) {
1032
+ $fileType = $this->_parseLongname($longname);
1033
+ if ($fileType) {
1034
+ $attributes['type'] = $fileType;
1035
+ }
1036
+ }
1037
+ $contents[$shortname] = $attributes + array('filename' => $shortname);
1038
+
1039
+ if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
1040
+ $this->_update_stat_cache($dir . '/' . $shortname, array());
1041
+ } else {
1042
+ if ($shortname == '..') {
1043
+ $temp = $this->_realpath($dir . '/..') . '/.';
1044
+ } else {
1045
+ $temp = $dir . '/' . $shortname;
1046
+ }
1047
+ $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
1048
+ }
1049
+ // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
1050
+ // final SSH_FXP_STATUS packet should tell us that, already.
1051
+ }
1052
+ break;
1053
+ case NET_SFTP_STATUS:
1054
+ if (strlen($response) < 4) {
1055
+ return false;
1056
+ }
1057
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1058
+ if ($status != NET_SFTP_STATUS_EOF) {
1059
+ $this->_logError($response, $status);
1060
+ return false;
1061
+ }
1062
+ break 2;
1063
+ default:
1064
+ user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1065
+ return false;
1066
+ }
1067
+ }
1068
+
1069
+ if (!$this->_close_handle($handle)) {
1070
+ return false;
1071
+ }
1072
+
1073
+ if (count($this->sortOptions)) {
1074
+ uasort($contents, array(&$this, '_comparator'));
1075
+ }
1076
+
1077
+ return $raw ? $contents : array_keys($contents);
1078
+ }
1079
+
1080
+ /**
1081
+ * Compares two rawlist entries using parameters set by setListOrder()
1082
+ *
1083
+ * Intended for use with uasort()
1084
+ *
1085
+ * @param array $a
1086
+ * @param array $b
1087
+ * @return int
1088
+ * @access private
1089
+ */
1090
+ function _comparator($a, $b)
1091
+ {
1092
+ switch (true) {
1093
+ case $a['filename'] === '.' || $b['filename'] === '.':
1094
+ if ($a['filename'] === $b['filename']) {
1095
+ return 0;
1096
+ }
1097
+ return $a['filename'] === '.' ? -1 : 1;
1098
+ case $a['filename'] === '..' || $b['filename'] === '..':
1099
+ if ($a['filename'] === $b['filename']) {
1100
+ return 0;
1101
+ }
1102
+ return $a['filename'] === '..' ? -1 : 1;
1103
+ case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
1104
+ if (!isset($b['type'])) {
1105
+ return 1;
1106
+ }
1107
+ if ($b['type'] !== $a['type']) {
1108
+ return -1;
1109
+ }
1110
+ break;
1111
+ case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
1112
+ return 1;
1113
+ }
1114
+ foreach ($this->sortOptions as $sort => $order) {
1115
+ if (!isset($a[$sort]) || !isset($b[$sort])) {
1116
+ if (isset($a[$sort])) {
1117
+ return -1;
1118
+ }
1119
+ if (isset($b[$sort])) {
1120
+ return 1;
1121
+ }
1122
+ return 0;
1123
+ }
1124
+ switch ($sort) {
1125
+ case 'filename':
1126
+ $result = strcasecmp($a['filename'], $b['filename']);
1127
+ if ($result) {
1128
+ return $order === SORT_DESC ? -$result : $result;
1129
+ }
1130
+ break;
1131
+ case 'permissions':
1132
+ case 'mode':
1133
+ $a[$sort]&= 07777;
1134
+ $b[$sort]&= 07777;
1135
+ default:
1136
+ if ($a[$sort] === $b[$sort]) {
1137
+ break;
1138
+ }
1139
+ return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * Defines how nlist() and rawlist() will be sorted - if at all.
1146
+ *
1147
+ * If sorting is enabled directories and files will be sorted independently with
1148
+ * directories appearing before files in the resultant array that is returned.
1149
+ *
1150
+ * Any parameter returned by stat is a valid sort parameter for this function.
1151
+ * Filename comparisons are case insensitive.
1152
+ *
1153
+ * Examples:
1154
+ *
1155
+ * $sftp->setListOrder('filename', SORT_ASC);
1156
+ * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
1157
+ * $sftp->setListOrder(true);
1158
+ * Separates directories from files but doesn't do any sorting beyond that
1159
+ * $sftp->setListOrder();
1160
+ * Don't do any sort of sorting
1161
+ *
1162
+ * @access public
1163
+ */
1164
+ function setListOrder()
1165
+ {
1166
+ $this->sortOptions = array();
1167
+ $args = func_get_args();
1168
+ if (empty($args)) {
1169
+ return;
1170
+ }
1171
+ $len = count($args) & 0x7FFFFFFE;
1172
+ for ($i = 0; $i < $len; $i+=2) {
1173
+ $this->sortOptions[$args[$i]] = $args[$i + 1];
1174
+ }
1175
+ if (!count($this->sortOptions)) {
1176
+ $this->sortOptions = array('bogus' => true);
1177
+ }
1178
+ }
1179
+
1180
+ /**
1181
+ * Returns the file size, in bytes, or false, on failure
1182
+ *
1183
+ * Files larger than 4GB will show up as being exactly 4GB.
1184
+ *
1185
+ * @param string $filename
1186
+ * @return mixed
1187
+ * @access public
1188
+ */
1189
+ function size($filename)
1190
+ {
1191
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1192
+ return false;
1193
+ }
1194
+
1195
+ $result = $this->stat($filename);
1196
+ if ($result === false) {
1197
+ return false;
1198
+ }
1199
+ return isset($result['size']) ? $result['size'] : -1;
1200
+ }
1201
+
1202
+ /**
1203
+ * Save files / directories to cache
1204
+ *
1205
+ * @param string $path
1206
+ * @param mixed $value
1207
+ * @access private
1208
+ */
1209
+ function _update_stat_cache($path, $value)
1210
+ {
1211
+ if ($this->use_stat_cache === false) {
1212
+ return;
1213
+ }
1214
+
1215
+ // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
1216
+ $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1217
+
1218
+ $temp = &$this->stat_cache;
1219
+ $max = count($dirs) - 1;
1220
+ foreach ($dirs as $i => $dir) {
1221
+ // if $temp is an object that means one of two things.
1222
+ // 1. a file was deleted and changed to a directory behind phpseclib's back
1223
+ // 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
1224
+ if (is_object($temp)) {
1225
+ $temp = array();
1226
+ }
1227
+ if (!isset($temp[$dir])) {
1228
+ $temp[$dir] = array();
1229
+ }
1230
+ if ($i === $max) {
1231
+ if (is_object($temp[$dir]) && is_object($value)) {
1232
+ if (!isset($value->stat) && isset($temp[$dir]->stat)) {
1233
+ $value->stat = $temp[$dir]->stat;
1234
+ }
1235
+ if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
1236
+ $value->lstat = $temp[$dir]->lstat;
1237
+ }
1238
+ }
1239
+ $temp[$dir] = $value;
1240
+ break;
1241
+ }
1242
+ $temp = &$temp[$dir];
1243
+ }
1244
+ }
1245
+
1246
+ /**
1247
+ * Remove files / directories from cache
1248
+ *
1249
+ * @param string $path
1250
+ * @return bool
1251
+ * @access private
1252
+ */
1253
+ function _remove_from_stat_cache($path)
1254
+ {
1255
+ $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1256
+
1257
+ $temp = &$this->stat_cache;
1258
+ $max = count($dirs) - 1;
1259
+ foreach ($dirs as $i => $dir) {
1260
+ if (!is_array($temp)) {
1261
+ return false;
1262
+ }
1263
+ if ($i === $max) {
1264
+ unset($temp[$dir]);
1265
+ return true;
1266
+ }
1267
+ if (!isset($temp[$dir])) {
1268
+ return false;
1269
+ }
1270
+ $temp = &$temp[$dir];
1271
+ }
1272
+ }
1273
+
1274
+ /**
1275
+ * Checks cache for path
1276
+ *
1277
+ * Mainly used by file_exists
1278
+ *
1279
+ * @param string $dir
1280
+ * @return mixed
1281
+ * @access private
1282
+ */
1283
+ function _query_stat_cache($path)
1284
+ {
1285
+ $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
1286
+
1287
+ $temp = &$this->stat_cache;
1288
+ foreach ($dirs as $dir) {
1289
+ if (!is_array($temp)) {
1290
+ return null;
1291
+ }
1292
+ if (!isset($temp[$dir])) {
1293
+ return null;
1294
+ }
1295
+ $temp = &$temp[$dir];
1296
+ }
1297
+ return $temp;
1298
+ }
1299
+
1300
+ /**
1301
+ * Returns general information about a file.
1302
+ *
1303
+ * Returns an array on success and false otherwise.
1304
+ *
1305
+ * @param string $filename
1306
+ * @return mixed
1307
+ * @access public
1308
+ */
1309
+ function stat($filename)
1310
+ {
1311
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1312
+ return false;
1313
+ }
1314
+
1315
+ $filename = $this->_realpath($filename);
1316
+ if ($filename === false) {
1317
+ return false;
1318
+ }
1319
+
1320
+ if ($this->use_stat_cache) {
1321
+ $result = $this->_query_stat_cache($filename);
1322
+ if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
1323
+ return $result['.']->stat;
1324
+ }
1325
+ if (is_object($result) && isset($result->stat)) {
1326
+ return $result->stat;
1327
+ }
1328
+ }
1329
+
1330
+ $stat = $this->_stat($filename, NET_SFTP_STAT);
1331
+ if ($stat === false) {
1332
+ $this->_remove_from_stat_cache($filename);
1333
+ return false;
1334
+ }
1335
+ if (isset($stat['type'])) {
1336
+ if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1337
+ $filename.= '/.';
1338
+ }
1339
+ $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1340
+ return $stat;
1341
+ }
1342
+
1343
+ $pwd = $this->pwd;
1344
+ $stat['type'] = $this->chdir($filename) ?
1345
+ NET_SFTP_TYPE_DIRECTORY :
1346
+ NET_SFTP_TYPE_REGULAR;
1347
+ $this->pwd = $pwd;
1348
+
1349
+ if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1350
+ $filename.= '/.';
1351
+ }
1352
+ $this->_update_stat_cache($filename, (object) array('stat' => $stat));
1353
+
1354
+ return $stat;
1355
+ }
1356
+
1357
+ /**
1358
+ * Returns general information about a file or symbolic link.
1359
+ *
1360
+ * Returns an array on success and false otherwise.
1361
+ *
1362
+ * @param string $filename
1363
+ * @return mixed
1364
+ * @access public
1365
+ */
1366
+ function lstat($filename)
1367
+ {
1368
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1369
+ return false;
1370
+ }
1371
+
1372
+ $filename = $this->_realpath($filename);
1373
+ if ($filename === false) {
1374
+ return false;
1375
+ }
1376
+
1377
+ if ($this->use_stat_cache) {
1378
+ $result = $this->_query_stat_cache($filename);
1379
+ if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
1380
+ return $result['.']->lstat;
1381
+ }
1382
+ if (is_object($result) && isset($result->lstat)) {
1383
+ return $result->lstat;
1384
+ }
1385
+ }
1386
+
1387
+ $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
1388
+ if ($lstat === false) {
1389
+ $this->_remove_from_stat_cache($filename);
1390
+ return false;
1391
+ }
1392
+ if (isset($lstat['type'])) {
1393
+ if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1394
+ $filename.= '/.';
1395
+ }
1396
+ $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1397
+ return $lstat;
1398
+ }
1399
+
1400
+ $stat = $this->_stat($filename, NET_SFTP_STAT);
1401
+
1402
+ if ($lstat != $stat) {
1403
+ $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
1404
+ $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1405
+ return $stat;
1406
+ }
1407
+
1408
+ $pwd = $this->pwd;
1409
+ $lstat['type'] = $this->chdir($filename) ?
1410
+ NET_SFTP_TYPE_DIRECTORY :
1411
+ NET_SFTP_TYPE_REGULAR;
1412
+ $this->pwd = $pwd;
1413
+
1414
+ if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
1415
+ $filename.= '/.';
1416
+ }
1417
+ $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
1418
+
1419
+ return $lstat;
1420
+ }
1421
+
1422
+ /**
1423
+ * Returns general information about a file or symbolic link
1424
+ *
1425
+ * Determines information without calling Net_SFTP::realpath().
1426
+ * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
1427
+ *
1428
+ * @param string $filename
1429
+ * @param int $type
1430
+ * @return mixed
1431
+ * @access private
1432
+ */
1433
+ function _stat($filename, $type)
1434
+ {
1435
+ // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1436
+ $packet = pack('Na*', strlen($filename), $filename);
1437
+ if (!$this->_send_sftp_packet($type, $packet)) {
1438
+ return false;
1439
+ }
1440
+
1441
+ $response = $this->_get_sftp_packet();
1442
+ switch ($this->packet_type) {
1443
+ case NET_SFTP_ATTRS:
1444
+ return $this->_parseAttributes($response);
1445
+ case NET_SFTP_STATUS:
1446
+ $this->_logError($response);
1447
+ return false;
1448
+ }
1449
+
1450
+ user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1451
+ return false;
1452
+ }
1453
+
1454
+ /**
1455
+ * Truncates a file to a given length
1456
+ *
1457
+ * @param string $filename
1458
+ * @param int $new_size
1459
+ * @return bool
1460
+ * @access public
1461
+ */
1462
+ function truncate($filename, $new_size)
1463
+ {
1464
+ $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
1465
+
1466
+ return $this->_setstat($filename, $attr, false);
1467
+ }
1468
+
1469
+ /**
1470
+ * Sets access and modification time of file.
1471
+ *
1472
+ * If the file does not exist, it will be created.
1473
+ *
1474
+ * @param string $filename
1475
+ * @param int $time
1476
+ * @param int $atime
1477
+ * @return bool
1478
+ * @access public
1479
+ */
1480
+ function touch($filename, $time = null, $atime = null)
1481
+ {
1482
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1483
+ return false;
1484
+ }
1485
+
1486
+ $filename = $this->_realpath($filename);
1487
+ if ($filename === false) {
1488
+ return false;
1489
+ }
1490
+
1491
+ if (!isset($time)) {
1492
+ $time = time();
1493
+ }
1494
+ if (!isset($atime)) {
1495
+ $atime = $time;
1496
+ }
1497
+
1498
+ $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
1499
+ $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
1500
+ $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
1501
+ if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
1502
+ return false;
1503
+ }
1504
+
1505
+ $response = $this->_get_sftp_packet();
1506
+ switch ($this->packet_type) {
1507
+ case NET_SFTP_HANDLE:
1508
+ return $this->_close_handle(substr($response, 4));
1509
+ case NET_SFTP_STATUS:
1510
+ $this->_logError($response);
1511
+ break;
1512
+ default:
1513
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
1514
+ return false;
1515
+ }
1516
+
1517
+ return $this->_setstat($filename, $attr, false);
1518
+ }
1519
+
1520
+ /**
1521
+ * Changes file or directory owner
1522
+ *
1523
+ * Returns true on success or false on error.
1524
+ *
1525
+ * @param string $filename
1526
+ * @param int $uid
1527
+ * @param bool $recursive
1528
+ * @return bool
1529
+ * @access public
1530
+ */
1531
+ function chown($filename, $uid, $recursive = false)
1532
+ {
1533
+ // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
1534
+ // "if the owner or group is specified as -1, then that ID is not changed"
1535
+ $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
1536
+
1537
+ return $this->_setstat($filename, $attr, $recursive);
1538
+ }
1539
+
1540
+ /**
1541
+ * Changes file or directory group
1542
+ *
1543
+ * Returns true on success or false on error.
1544
+ *
1545
+ * @param string $filename
1546
+ * @param int $gid
1547
+ * @param bool $recursive
1548
+ * @return bool
1549
+ * @access public
1550
+ */
1551
+ function chgrp($filename, $gid, $recursive = false)
1552
+ {
1553
+ $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
1554
+
1555
+ return $this->_setstat($filename, $attr, $recursive);
1556
+ }
1557
+
1558
+ /**
1559
+ * Set permissions on a file.
1560
+ *
1561
+ * Returns the new file permissions on success or false on error.
1562
+ * If $recursive is true than this just returns true or false.
1563
+ *
1564
+ * @param int $mode
1565
+ * @param string $filename
1566
+ * @param bool $recursive
1567
+ * @return mixed
1568
+ * @access public
1569
+ */
1570
+ function chmod($mode, $filename, $recursive = false)
1571
+ {
1572
+ if (is_string($mode) && is_int($filename)) {
1573
+ $temp = $mode;
1574
+ $mode = $filename;
1575
+ $filename = $temp;
1576
+ }
1577
+
1578
+ $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
1579
+ if (!$this->_setstat($filename, $attr, $recursive)) {
1580
+ return false;
1581
+ }
1582
+ if ($recursive) {
1583
+ return true;
1584
+ }
1585
+
1586
+ $filename = $this->realpath($filename);
1587
+ // rather than return what the permissions *should* be, we'll return what they actually are. this will also
1588
+ // tell us if the file actually exists.
1589
+ // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
1590
+ $packet = pack('Na*', strlen($filename), $filename);
1591
+ if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
1592
+ return false;
1593
+ }
1594
+
1595
+ $response = $this->_get_sftp_packet();
1596
+ switch ($this->packet_type) {
1597
+ case NET_SFTP_ATTRS:
1598
+ $attrs = $this->_parseAttributes($response);
1599
+ return $attrs['permissions'];
1600
+ case NET_SFTP_STATUS:
1601
+ $this->_logError($response);
1602
+ return false;
1603
+ }
1604
+
1605
+ user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
1606
+ return false;
1607
+ }
1608
+
1609
+ /**
1610
+ * Sets information about a file
1611
+ *
1612
+ * @param string $filename
1613
+ * @param string $attr
1614
+ * @param bool $recursive
1615
+ * @return bool
1616
+ * @access private
1617
+ */
1618
+ function _setstat($filename, $attr, $recursive)
1619
+ {
1620
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1621
+ return false;
1622
+ }
1623
+
1624
+ $filename = $this->_realpath($filename);
1625
+ if ($filename === false) {
1626
+ return false;
1627
+ }
1628
+
1629
+ $this->_remove_from_stat_cache($filename);
1630
+
1631
+ if ($recursive) {
1632
+ $i = 0;
1633
+ $result = $this->_setstat_recursive($filename, $attr, $i);
1634
+ $this->_read_put_responses($i);
1635
+ return $result;
1636
+ }
1637
+
1638
+ // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
1639
+ // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
1640
+ if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
1641
+ return false;
1642
+ }
1643
+
1644
+ /*
1645
+ "Because some systems must use separate system calls to set various attributes, it is possible that a failure
1646
+ response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
1647
+ servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
1648
+
1649
+ -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
1650
+ */
1651
+ $response = $this->_get_sftp_packet();
1652
+ if ($this->packet_type != NET_SFTP_STATUS) {
1653
+ user_error('Expected SSH_FXP_STATUS');
1654
+ return false;
1655
+ }
1656
+
1657
+ if (strlen($response) < 4) {
1658
+ return false;
1659
+ }
1660
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1661
+ if ($status != NET_SFTP_STATUS_OK) {
1662
+ $this->_logError($response, $status);
1663
+ return false;
1664
+ }
1665
+
1666
+ return true;
1667
+ }
1668
+
1669
+ /**
1670
+ * Recursively sets information on directories on the SFTP server
1671
+ *
1672
+ * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
1673
+ *
1674
+ * @param string $path
1675
+ * @param string $attr
1676
+ * @param int $i
1677
+ * @return bool
1678
+ * @access private
1679
+ */
1680
+ function _setstat_recursive($path, $attr, &$i)
1681
+ {
1682
+ if (!$this->_read_put_responses($i)) {
1683
+ return false;
1684
+ }
1685
+ $i = 0;
1686
+ $entries = $this->_list($path, true);
1687
+
1688
+ if ($entries === false) {
1689
+ return $this->_setstat($path, $attr, false);
1690
+ }
1691
+
1692
+ // normally $entries would have at least . and .. but it might not if the directories
1693
+ // permissions didn't allow reading
1694
+ if (empty($entries)) {
1695
+ return false;
1696
+ }
1697
+
1698
+ unset($entries['.'], $entries['..']);
1699
+ foreach ($entries as $filename => $props) {
1700
+ if (!isset($props['type'])) {
1701
+ return false;
1702
+ }
1703
+
1704
+ $temp = $path . '/' . $filename;
1705
+ if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
1706
+ if (!$this->_setstat_recursive($temp, $attr, $i)) {
1707
+ return false;
1708
+ }
1709
+ } else {
1710
+ if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
1711
+ return false;
1712
+ }
1713
+
1714
+ $i++;
1715
+
1716
+ if ($i >= NET_SFTP_QUEUE_SIZE) {
1717
+ if (!$this->_read_put_responses($i)) {
1718
+ return false;
1719
+ }
1720
+ $i = 0;
1721
+ }
1722
+ }
1723
+ }
1724
+
1725
+ if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
1726
+ return false;
1727
+ }
1728
+
1729
+ $i++;
1730
+
1731
+ if ($i >= NET_SFTP_QUEUE_SIZE) {
1732
+ if (!$this->_read_put_responses($i)) {
1733
+ return false;
1734
+ }
1735
+ $i = 0;
1736
+ }
1737
+
1738
+ return true;
1739
+ }
1740
+
1741
+ /**
1742
+ * Return the target of a symbolic link
1743
+ *
1744
+ * @param string $link
1745
+ * @return mixed
1746
+ * @access public
1747
+ */
1748
+ function readlink($link)
1749
+ {
1750
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1751
+ return false;
1752
+ }
1753
+
1754
+ $link = $this->_realpath($link);
1755
+
1756
+ if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
1757
+ return false;
1758
+ }
1759
+
1760
+ $response = $this->_get_sftp_packet();
1761
+ switch ($this->packet_type) {
1762
+ case NET_SFTP_NAME:
1763
+ break;
1764
+ case NET_SFTP_STATUS:
1765
+ $this->_logError($response);
1766
+ return false;
1767
+ default:
1768
+ user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
1769
+ return false;
1770
+ }
1771
+
1772
+ if (strlen($response) < 4) {
1773
+ return false;
1774
+ }
1775
+ extract(unpack('Ncount', $this->_string_shift($response, 4)));
1776
+ // the file isn't a symlink
1777
+ if (!$count) {
1778
+ return false;
1779
+ }
1780
+
1781
+ if (strlen($response) < 4) {
1782
+ return false;
1783
+ }
1784
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
1785
+ return $this->_string_shift($response, $length);
1786
+ }
1787
+
1788
+ /**
1789
+ * Create a symlink
1790
+ *
1791
+ * symlink() creates a symbolic link to the existing target with the specified name link.
1792
+ *
1793
+ * @param string $target
1794
+ * @param string $link
1795
+ * @return bool
1796
+ * @access public
1797
+ */
1798
+ function symlink($target, $link)
1799
+ {
1800
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1801
+ return false;
1802
+ }
1803
+
1804
+ //$target = $this->_realpath($target);
1805
+ $link = $this->_realpath($link);
1806
+
1807
+ $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
1808
+ if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
1809
+ return false;
1810
+ }
1811
+
1812
+ $response = $this->_get_sftp_packet();
1813
+ if ($this->packet_type != NET_SFTP_STATUS) {
1814
+ user_error('Expected SSH_FXP_STATUS');
1815
+ return false;
1816
+ }
1817
+
1818
+ if (strlen($response) < 4) {
1819
+ return false;
1820
+ }
1821
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1822
+ if ($status != NET_SFTP_STATUS_OK) {
1823
+ $this->_logError($response, $status);
1824
+ return false;
1825
+ }
1826
+
1827
+ return true;
1828
+ }
1829
+
1830
+ /**
1831
+ * Creates a directory.
1832
+ *
1833
+ * @param string $dir
1834
+ * @return bool
1835
+ * @access public
1836
+ */
1837
+ function mkdir($dir, $mode = -1, $recursive = false)
1838
+ {
1839
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1840
+ return false;
1841
+ }
1842
+
1843
+ $dir = $this->_realpath($dir);
1844
+
1845
+ if ($recursive) {
1846
+ $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
1847
+ if (empty($dirs[0])) {
1848
+ array_shift($dirs);
1849
+ $dirs[0] = '/' . $dirs[0];
1850
+ }
1851
+ for ($i = 0; $i < count($dirs); $i++) {
1852
+ $temp = array_slice($dirs, 0, $i + 1);
1853
+ $temp = implode('/', $temp);
1854
+ $result = $this->_mkdir_helper($temp, $mode);
1855
+ }
1856
+ return $result;
1857
+ }
1858
+
1859
+ return $this->_mkdir_helper($dir, $mode);
1860
+ }
1861
+
1862
+ /**
1863
+ * Helper function for directory creation
1864
+ *
1865
+ * @param string $dir
1866
+ * @return bool
1867
+ * @access private
1868
+ */
1869
+ function _mkdir_helper($dir, $mode)
1870
+ {
1871
+ // send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
1872
+ if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, "\0\0\0\0"))) {
1873
+ return false;
1874
+ }
1875
+
1876
+ $response = $this->_get_sftp_packet();
1877
+ if ($this->packet_type != NET_SFTP_STATUS) {
1878
+ user_error('Expected SSH_FXP_STATUS');
1879
+ return false;
1880
+ }
1881
+
1882
+ if (strlen($response) < 4) {
1883
+ return false;
1884
+ }
1885
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1886
+ if ($status != NET_SFTP_STATUS_OK) {
1887
+ $this->_logError($response, $status);
1888
+ return false;
1889
+ }
1890
+
1891
+ if ($mode !== -1) {
1892
+ $this->chmod($mode, $dir);
1893
+ }
1894
+
1895
+ return true;
1896
+ }
1897
+
1898
+ /**
1899
+ * Removes a directory.
1900
+ *
1901
+ * @param string $dir
1902
+ * @return bool
1903
+ * @access public
1904
+ */
1905
+ function rmdir($dir)
1906
+ {
1907
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1908
+ return false;
1909
+ }
1910
+
1911
+ $dir = $this->_realpath($dir);
1912
+ if ($dir === false) {
1913
+ return false;
1914
+ }
1915
+
1916
+ if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
1917
+ return false;
1918
+ }
1919
+
1920
+ $response = $this->_get_sftp_packet();
1921
+ if ($this->packet_type != NET_SFTP_STATUS) {
1922
+ user_error('Expected SSH_FXP_STATUS');
1923
+ return false;
1924
+ }
1925
+
1926
+ if (strlen($response) < 4) {
1927
+ return false;
1928
+ }
1929
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
1930
+ if ($status != NET_SFTP_STATUS_OK) {
1931
+ // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
1932
+ $this->_logError($response, $status);
1933
+ return false;
1934
+ }
1935
+
1936
+ $this->_remove_from_stat_cache($dir);
1937
+ // the following will do a soft delete, which would be useful if you deleted a file
1938
+ // and then tried to do a stat on the deleted file. the above, in contrast, does
1939
+ // a hard delete
1940
+ //$this->_update_stat_cache($dir, false);
1941
+
1942
+ return true;
1943
+ }
1944
+
1945
+ /**
1946
+ * Uploads a file to the SFTP server.
1947
+ *
1948
+ * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
1949
+ * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
1950
+ * long, containing 'filename.ext' as its contents.
1951
+ *
1952
+ * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
1953
+ * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
1954
+ * large $remote_file will be, as well.
1955
+ *
1956
+ * If $data is a resource then it'll be used as a resource instead.
1957
+ *
1958
+ *
1959
+ * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number
1960
+ * of bytes to return, and returns a string if there is some data or null if there is no more data
1961
+ *
1962
+ * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
1963
+ * care of that, yourself.
1964
+ *
1965
+ * $mode can take an additional two parameters - NET_SFTP_RESUME and NET_SFTP_RESUME_START. These are bitwise AND'd with
1966
+ * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
1967
+ *
1968
+ * NET_SFTP_LOCAL_FILE | NET_SFTP_RESUME
1969
+ *
1970
+ * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
1971
+ * NET_SFTP_RESUME with NET_SFTP_RESUME_START.
1972
+ *
1973
+ * If $mode & (NET_SFTP_RESUME | NET_SFTP_RESUME_START) then NET_SFTP_RESUME_START will be assumed.
1974
+ *
1975
+ * $start and $local_start give you more fine grained control over this process and take precident over NET_SFTP_RESUME
1976
+ * when they're non-negative. ie. $start could let you write at the end of a file (like NET_SFTP_RESUME) or in the middle
1977
+ * of one. $local_start could let you start your reading from the end of a file (like NET_SFTP_RESUME_START) or in the
1978
+ * middle of one.
1979
+ *
1980
+ * Setting $local_start to > 0 or $mode | NET_SFTP_RESUME_START doesn't do anything unless $mode | NET_SFTP_LOCAL_FILE.
1981
+ *
1982
+ * @param string $remote_file
1983
+ * @param string|resource $data
1984
+ * @param int $mode
1985
+ * @param int $start
1986
+ * @param int $local_start
1987
+ * @param callable|null $progressCallback
1988
+ * @return bool
1989
+ * @access public
1990
+ * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
1991
+ */
1992
+ function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null)
1993
+ {
1994
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
1995
+ return false;
1996
+ }
1997
+
1998
+ $remote_file = $this->_realpath($remote_file);
1999
+ if ($remote_file === false) {
2000
+ return false;
2001
+ }
2002
+
2003
+ $this->_remove_from_stat_cache($remote_file);
2004
+
2005
+ $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
2006
+ // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
2007
+ // in practice, it doesn't seem to do that.
2008
+ //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
2009
+
2010
+ if ($start >= 0) {
2011
+ $offset = $start;
2012
+ } elseif ($mode & NET_SFTP_RESUME) {
2013
+ // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
2014
+ $size = $this->size($remote_file);
2015
+ $offset = $size !== false ? $size : 0;
2016
+ } else {
2017
+ $offset = 0;
2018
+ $flags|= NET_SFTP_OPEN_TRUNCATE;
2019
+ }
2020
+
2021
+ $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
2022
+ if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2023
+ return false;
2024
+ }
2025
+
2026
+ $response = $this->_get_sftp_packet();
2027
+ switch ($this->packet_type) {
2028
+ case NET_SFTP_HANDLE:
2029
+ $handle = substr($response, 4);
2030
+ break;
2031
+ case NET_SFTP_STATUS:
2032
+ $this->_logError($response);
2033
+ return false;
2034
+ default:
2035
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2036
+ return false;
2037
+ }
2038
+
2039
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
2040
+ $dataCallback = false;
2041
+ switch (true) {
2042
+ case $mode & NET_SFTP_CALLBACK:
2043
+ if (!is_callable($data)) {
2044
+ user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag");
2045
+ }
2046
+ $dataCallback = $data;
2047
+ // do nothing
2048
+ break;
2049
+ case is_resource($data):
2050
+ $mode = $mode & ~NET_SFTP_LOCAL_FILE;
2051
+ $info = stream_get_meta_data($data);
2052
+ if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
2053
+ $fp = fopen('php://memory', 'w+');
2054
+ stream_copy_to_stream($data, $fp);
2055
+ rewind($fp);
2056
+ } else {
2057
+ $fp = $data;
2058
+ }
2059
+ break;
2060
+ case $mode & NET_SFTP_LOCAL_FILE:
2061
+ if (!is_file($data)) {
2062
+ user_error("$data is not a valid file");
2063
+ return false;
2064
+ }
2065
+ $fp = @fopen($data, 'rb');
2066
+ if (!$fp) {
2067
+ return false;
2068
+ }
2069
+ }
2070
+
2071
+ if (isset($fp)) {
2072
+ $stat = fstat($fp);
2073
+ $size = !empty($stat) ? $stat['size'] : 0;
2074
+
2075
+ if ($local_start >= 0) {
2076
+ fseek($fp, $local_start);
2077
+ $size-= $local_start;
2078
+ }
2079
+ } elseif ($dataCallback) {
2080
+ $size = 0;
2081
+ } else {
2082
+ $size = strlen($data);
2083
+ }
2084
+
2085
+ $sent = 0;
2086
+ $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
2087
+
2088
+ $sftp_packet_size = 4096; // PuTTY uses 4096
2089
+ // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
2090
+ $sftp_packet_size-= strlen($handle) + 25;
2091
+ $i = $j = 0;
2092
+ while ($dataCallback || ($size === 0 || $sent < $size)) {
2093
+ if ($dataCallback) {
2094
+ $temp = call_user_func($dataCallback, $sftp_packet_size);
2095
+ if (is_null($temp)) {
2096
+ break;
2097
+ }
2098
+ } else {
2099
+ $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
2100
+ if ($temp === false || $temp === '') {
2101
+ break;
2102
+ }
2103
+ }
2104
+
2105
+ $subtemp = $offset + $sent;
2106
+ $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
2107
+ if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) {
2108
+ if ($mode & NET_SFTP_LOCAL_FILE) {
2109
+ fclose($fp);
2110
+ }
2111
+ return false;
2112
+ }
2113
+ $sent+= strlen($temp);
2114
+ if (is_callable($progressCallback)) {
2115
+ call_user_func($progressCallback, $sent);
2116
+ }
2117
+
2118
+ $i++;
2119
+ $j++;
2120
+
2121
+ if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) {
2122
+ if (!$this->_read_put_responses($i)) {
2123
+ $i = 0;
2124
+ break;
2125
+ }
2126
+ $i = 0;
2127
+ }
2128
+ }
2129
+
2130
+ if (!$this->_read_put_responses($i)) {
2131
+ if ($mode & NET_SFTP_LOCAL_FILE) {
2132
+ fclose($fp);
2133
+ }
2134
+ $this->_close_handle($handle);
2135
+ return false;
2136
+ }
2137
+
2138
+ if ($mode & NET_SFTP_LOCAL_FILE) {
2139
+ fclose($fp);
2140
+ }
2141
+
2142
+ return $this->_close_handle($handle);
2143
+ }
2144
+
2145
+ /**
2146
+ * Reads multiple successive SSH_FXP_WRITE responses
2147
+ *
2148
+ * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
2149
+ * SSH_FXP_WRITEs, in succession, and then reading $i responses.
2150
+ *
2151
+ * @param int $i
2152
+ * @return bool
2153
+ * @access private
2154
+ */
2155
+ function _read_put_responses($i)
2156
+ {
2157
+ while ($i--) {
2158
+ $response = $this->_get_sftp_packet();
2159
+ if ($this->packet_type != NET_SFTP_STATUS) {
2160
+ user_error('Expected SSH_FXP_STATUS');
2161
+ return false;
2162
+ }
2163
+
2164
+ if (strlen($response) < 4) {
2165
+ return false;
2166
+ }
2167
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2168
+ if ($status != NET_SFTP_STATUS_OK) {
2169
+ $this->_logError($response, $status);
2170
+ break;
2171
+ }
2172
+ }
2173
+
2174
+ return $i < 0;
2175
+ }
2176
+
2177
+ /**
2178
+ * Close handle
2179
+ *
2180
+ * @param string $handle
2181
+ * @return bool
2182
+ * @access private
2183
+ */
2184
+ function _close_handle($handle)
2185
+ {
2186
+ if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
2187
+ return false;
2188
+ }
2189
+
2190
+ // "The client MUST release all resources associated with the handle regardless of the status."
2191
+ // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
2192
+ $response = $this->_get_sftp_packet();
2193
+ if ($this->packet_type != NET_SFTP_STATUS) {
2194
+ user_error('Expected SSH_FXP_STATUS');
2195
+ return false;
2196
+ }
2197
+
2198
+ if (strlen($response) < 4) {
2199
+ return false;
2200
+ }
2201
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2202
+ if ($status != NET_SFTP_STATUS_OK) {
2203
+ $this->_logError($response, $status);
2204
+ return false;
2205
+ }
2206
+
2207
+ return true;
2208
+ }
2209
+
2210
+ /**
2211
+ * Downloads a file from the SFTP server.
2212
+ *
2213
+ * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
2214
+ * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
2215
+ * operation.
2216
+ *
2217
+ * $offset and $length can be used to download files in chunks.
2218
+ *
2219
+ * @param string $remote_file
2220
+ * @param string $local_file
2221
+ * @param int $offset
2222
+ * @param int $length
2223
+ * @param callable|null $progressCallback
2224
+ * @return mixed
2225
+ * @access public
2226
+ */
2227
+ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
2228
+ {
2229
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2230
+ return false;
2231
+ }
2232
+
2233
+ $remote_file = $this->_realpath($remote_file);
2234
+ if ($remote_file === false) {
2235
+ return false;
2236
+ }
2237
+
2238
+ $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
2239
+ if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2240
+ return false;
2241
+ }
2242
+
2243
+ $response = $this->_get_sftp_packet();
2244
+ switch ($this->packet_type) {
2245
+ case NET_SFTP_HANDLE:
2246
+ $handle = substr($response, 4);
2247
+ break;
2248
+ case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2249
+ $this->_logError($response);
2250
+ return false;
2251
+ default:
2252
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2253
+ return false;
2254
+ }
2255
+
2256
+ if (is_resource($local_file)) {
2257
+ $fp = $local_file;
2258
+ $stat = fstat($fp);
2259
+ $res_offset = $stat['size'];
2260
+ } else {
2261
+ $res_offset = 0;
2262
+ if ($local_file !== false) {
2263
+ $fp = fopen($local_file, 'wb');
2264
+ if (!$fp) {
2265
+ return false;
2266
+ }
2267
+ } else {
2268
+ $content = '';
2269
+ }
2270
+ }
2271
+
2272
+ $fclose_check = $local_file !== false && !is_resource($local_file);
2273
+
2274
+ $start = $offset;
2275
+ $read = 0;
2276
+ while (true) {
2277
+ $i = 0;
2278
+
2279
+ while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
2280
+ $tempoffset = $start + $read;
2281
+
2282
+ $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
2283
+
2284
+ $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
2285
+ if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
2286
+ if ($fclose_check) {
2287
+ fclose($fp);
2288
+ }
2289
+ return false;
2290
+ }
2291
+ $packet = null;
2292
+ $read+= $packet_size;
2293
+ if (is_callable($progressCallback)) {
2294
+ call_user_func($progressCallback, $read);
2295
+ }
2296
+ $i++;
2297
+ }
2298
+
2299
+ if (!$i) {
2300
+ break;
2301
+ }
2302
+
2303
+ $packets_sent = $i - 1;
2304
+
2305
+ $clear_responses = false;
2306
+ while ($i > 0) {
2307
+ $i--;
2308
+
2309
+ if ($clear_responses) {
2310
+ $this->_get_sftp_packet($packets_sent - $i);
2311
+ continue;
2312
+ } else {
2313
+ $response = $this->_get_sftp_packet($packets_sent - $i);
2314
+ }
2315
+
2316
+ switch ($this->packet_type) {
2317
+ case NET_SFTP_DATA:
2318
+ $temp = substr($response, 4);
2319
+ $offset+= strlen($temp);
2320
+ if ($local_file === false) {
2321
+ $content.= $temp;
2322
+ } else {
2323
+ fputs($fp, $temp);
2324
+ }
2325
+ $temp = null;
2326
+ break;
2327
+ case NET_SFTP_STATUS:
2328
+ // could, in theory, return false if !strlen($content) but we'll hold off for the time being
2329
+ $this->_logError($response);
2330
+ $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
2331
+ break;
2332
+ default:
2333
+ if ($fclose_check) {
2334
+ fclose($fp);
2335
+ }
2336
+ user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
2337
+ }
2338
+ $response = null;
2339
+ }
2340
+
2341
+ if ($clear_responses) {
2342
+ break;
2343
+ }
2344
+ }
2345
+
2346
+ if ($length > 0 && $length <= $offset - $start) {
2347
+ if ($local_file === false) {
2348
+ $content = substr($content, 0, $length);
2349
+ } else {
2350
+ ftruncate($fp, $length + $res_offset);
2351
+ }
2352
+ }
2353
+
2354
+ if ($fclose_check) {
2355
+ fclose($fp);
2356
+ }
2357
+
2358
+ if (!$this->_close_handle($handle)) {
2359
+ return false;
2360
+ }
2361
+
2362
+ // if $content isn't set that means a file was written to
2363
+ return isset($content) ? $content : true;
2364
+ }
2365
+
2366
+ /**
2367
+ * Deletes a file on the SFTP server.
2368
+ *
2369
+ * @param string $path
2370
+ * @param bool $recursive
2371
+ * @return bool
2372
+ * @access public
2373
+ */
2374
+ function delete($path, $recursive = true)
2375
+ {
2376
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2377
+ return false;
2378
+ }
2379
+
2380
+ if (is_object($path)) {
2381
+ // It's an object. Cast it as string before we check anything else.
2382
+ $path = (string) $path;
2383
+ }
2384
+
2385
+ if (!is_string($path) || $path == '') {
2386
+ return false;
2387
+ }
2388
+
2389
+ $path = $this->_realpath($path);
2390
+ if ($path === false) {
2391
+ return false;
2392
+ }
2393
+
2394
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2395
+ if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
2396
+ return false;
2397
+ }
2398
+
2399
+ $response = $this->_get_sftp_packet();
2400
+ if ($this->packet_type != NET_SFTP_STATUS) {
2401
+ user_error('Expected SSH_FXP_STATUS');
2402
+ return false;
2403
+ }
2404
+
2405
+ // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2406
+ if (strlen($response) < 4) {
2407
+ return false;
2408
+ }
2409
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2410
+ if ($status != NET_SFTP_STATUS_OK) {
2411
+ $this->_logError($response, $status);
2412
+ if (!$recursive) {
2413
+ return false;
2414
+ }
2415
+ $i = 0;
2416
+ $result = $this->_delete_recursive($path, $i);
2417
+ $this->_read_put_responses($i);
2418
+ return $result;
2419
+ }
2420
+
2421
+ $this->_remove_from_stat_cache($path);
2422
+
2423
+ return true;
2424
+ }
2425
+
2426
+ /**
2427
+ * Recursively deletes directories on the SFTP server
2428
+ *
2429
+ * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
2430
+ *
2431
+ * @param string $path
2432
+ * @param int $i
2433
+ * @return bool
2434
+ * @access private
2435
+ */
2436
+ function _delete_recursive($path, &$i)
2437
+ {
2438
+ if (!$this->_read_put_responses($i)) {
2439
+ return false;
2440
+ }
2441
+ $i = 0;
2442
+ $entries = $this->_list($path, true);
2443
+
2444
+ // normally $entries would have at least . and .. but it might not if the directories
2445
+ // permissions didn't allow reading
2446
+ if (empty($entries)) {
2447
+ return false;
2448
+ }
2449
+
2450
+ unset($entries['.'], $entries['..']);
2451
+ foreach ($entries as $filename => $props) {
2452
+ if (!isset($props['type'])) {
2453
+ return false;
2454
+ }
2455
+
2456
+ $temp = $path . '/' . $filename;
2457
+ if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
2458
+ if (!$this->_delete_recursive($temp, $i)) {
2459
+ return false;
2460
+ }
2461
+ } else {
2462
+ if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
2463
+ return false;
2464
+ }
2465
+ $this->_remove_from_stat_cache($temp);
2466
+
2467
+ $i++;
2468
+
2469
+ if ($i >= NET_SFTP_QUEUE_SIZE) {
2470
+ if (!$this->_read_put_responses($i)) {
2471
+ return false;
2472
+ }
2473
+ $i = 0;
2474
+ }
2475
+ }
2476
+ }
2477
+
2478
+ if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
2479
+ return false;
2480
+ }
2481
+ $this->_remove_from_stat_cache($path);
2482
+
2483
+ $i++;
2484
+
2485
+ if ($i >= NET_SFTP_QUEUE_SIZE) {
2486
+ if (!$this->_read_put_responses($i)) {
2487
+ return false;
2488
+ }
2489
+ $i = 0;
2490
+ }
2491
+
2492
+ return true;
2493
+ }
2494
+
2495
+ /**
2496
+ * Checks whether a file or directory exists
2497
+ *
2498
+ * @param string $path
2499
+ * @return bool
2500
+ * @access public
2501
+ */
2502
+ function file_exists($path)
2503
+ {
2504
+ if ($this->use_stat_cache) {
2505
+ $path = $this->_realpath($path);
2506
+
2507
+ $result = $this->_query_stat_cache($path);
2508
+
2509
+ if (isset($result)) {
2510
+ // return true if $result is an array or if it's an stdClass object
2511
+ return $result !== false;
2512
+ }
2513
+ }
2514
+
2515
+ return $this->stat($path) !== false;
2516
+ }
2517
+
2518
+ /**
2519
+ * Tells whether the filename is a directory
2520
+ *
2521
+ * @param string $path
2522
+ * @return bool
2523
+ * @access public
2524
+ */
2525
+ function is_dir($path)
2526
+ {
2527
+ $result = $this->_get_stat_cache_prop($path, 'type');
2528
+ if ($result === false) {
2529
+ return false;
2530
+ }
2531
+ return $result === NET_SFTP_TYPE_DIRECTORY;
2532
+ }
2533
+
2534
+ /**
2535
+ * Tells whether the filename is a regular file
2536
+ *
2537
+ * @param string $path
2538
+ * @return bool
2539
+ * @access public
2540
+ */
2541
+ function is_file($path)
2542
+ {
2543
+ $result = $this->_get_stat_cache_prop($path, 'type');
2544
+ if ($result === false) {
2545
+ return false;
2546
+ }
2547
+ return $result === NET_SFTP_TYPE_REGULAR;
2548
+ }
2549
+
2550
+ /**
2551
+ * Tells whether the filename is a symbolic link
2552
+ *
2553
+ * @param string $path
2554
+ * @return bool
2555
+ * @access public
2556
+ */
2557
+ function is_link($path)
2558
+ {
2559
+ $result = $this->_get_lstat_cache_prop($path, 'type');
2560
+ if ($result === false) {
2561
+ return false;
2562
+ }
2563
+ return $result === NET_SFTP_TYPE_SYMLINK;
2564
+ }
2565
+
2566
+ /**
2567
+ * Tells whether a file exists and is readable
2568
+ *
2569
+ * @param string $path
2570
+ * @return bool
2571
+ * @access public
2572
+ */
2573
+ function is_readable($path)
2574
+ {
2575
+ $path = $this->_realpath($path);
2576
+
2577
+ $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
2578
+ if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2579
+ return false;
2580
+ }
2581
+
2582
+ $response = $this->_get_sftp_packet();
2583
+ switch ($this->packet_type) {
2584
+ case NET_SFTP_HANDLE:
2585
+ return true;
2586
+ case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2587
+ return false;
2588
+ default:
2589
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2590
+ return false;
2591
+ }
2592
+ }
2593
+
2594
+ /**
2595
+ * Tells whether the filename is writable
2596
+ *
2597
+ * @param string $path
2598
+ * @return bool
2599
+ * @access public
2600
+ */
2601
+ function is_writable($path)
2602
+ {
2603
+ $path = $this->_realpath($path);
2604
+
2605
+ $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
2606
+ if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
2607
+ return false;
2608
+ }
2609
+
2610
+ $response = $this->_get_sftp_packet();
2611
+ switch ($this->packet_type) {
2612
+ case NET_SFTP_HANDLE:
2613
+ return true;
2614
+ case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2615
+ return false;
2616
+ default:
2617
+ user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
2618
+ return false;
2619
+ }
2620
+ }
2621
+
2622
+ /**
2623
+ * Tells whether the filename is writeable
2624
+ *
2625
+ * Alias of is_writable
2626
+ *
2627
+ * @param string $path
2628
+ * @return bool
2629
+ * @access public
2630
+ */
2631
+ function is_writeable($path)
2632
+ {
2633
+ return $this->is_writable($path);
2634
+ }
2635
+
2636
+ /**
2637
+ * Gets last access time of file
2638
+ *
2639
+ * @param string $path
2640
+ * @return mixed
2641
+ * @access public
2642
+ */
2643
+ function fileatime($path)
2644
+ {
2645
+ return $this->_get_stat_cache_prop($path, 'atime');
2646
+ }
2647
+
2648
+ /**
2649
+ * Gets file modification time
2650
+ *
2651
+ * @param string $path
2652
+ * @return mixed
2653
+ * @access public
2654
+ */
2655
+ function filemtime($path)
2656
+ {
2657
+ return $this->_get_stat_cache_prop($path, 'mtime');
2658
+ }
2659
+
2660
+ /**
2661
+ * Gets file permissions
2662
+ *
2663
+ * @param string $path
2664
+ * @return mixed
2665
+ * @access public
2666
+ */
2667
+ function fileperms($path)
2668
+ {
2669
+ return $this->_get_stat_cache_prop($path, 'permissions');
2670
+ }
2671
+
2672
+ /**
2673
+ * Gets file owner
2674
+ *
2675
+ * @param string $path
2676
+ * @return mixed
2677
+ * @access public
2678
+ */
2679
+ function fileowner($path)
2680
+ {
2681
+ return $this->_get_stat_cache_prop($path, 'uid');
2682
+ }
2683
+
2684
+ /**
2685
+ * Gets file group
2686
+ *
2687
+ * @param string $path
2688
+ * @return mixed
2689
+ * @access public
2690
+ */
2691
+ function filegroup($path)
2692
+ {
2693
+ return $this->_get_stat_cache_prop($path, 'gid');
2694
+ }
2695
+
2696
+ /**
2697
+ * Gets file size
2698
+ *
2699
+ * @param string $path
2700
+ * @return mixed
2701
+ * @access public
2702
+ */
2703
+ function filesize($path)
2704
+ {
2705
+ return $this->_get_stat_cache_prop($path, 'size');
2706
+ }
2707
+
2708
+ /**
2709
+ * Gets file type
2710
+ *
2711
+ * @param string $path
2712
+ * @return mixed
2713
+ * @access public
2714
+ */
2715
+ function filetype($path)
2716
+ {
2717
+ $type = $this->_get_stat_cache_prop($path, 'type');
2718
+ if ($type === false) {
2719
+ return false;
2720
+ }
2721
+
2722
+ switch ($type) {
2723
+ case NET_SFTP_TYPE_BLOCK_DEVICE:
2724
+ return 'block';
2725
+ case NET_SFTP_TYPE_CHAR_DEVICE:
2726
+ return 'char';
2727
+ case NET_SFTP_TYPE_DIRECTORY:
2728
+ return 'dir';
2729
+ case NET_SFTP_TYPE_FIFO:
2730
+ return 'fifo';
2731
+ case NET_SFTP_TYPE_REGULAR:
2732
+ return 'file';
2733
+ case NET_SFTP_TYPE_SYMLINK:
2734
+ return 'link';
2735
+ default:
2736
+ return false;
2737
+ }
2738
+ }
2739
+
2740
+ /**
2741
+ * Return a stat properity
2742
+ *
2743
+ * Uses cache if appropriate.
2744
+ *
2745
+ * @param string $path
2746
+ * @param string $prop
2747
+ * @return mixed
2748
+ * @access private
2749
+ */
2750
+ function _get_stat_cache_prop($path, $prop)
2751
+ {
2752
+ return $this->_get_xstat_cache_prop($path, $prop, 'stat');
2753
+ }
2754
+
2755
+ /**
2756
+ * Return an lstat properity
2757
+ *
2758
+ * Uses cache if appropriate.
2759
+ *
2760
+ * @param string $path
2761
+ * @param string $prop
2762
+ * @return mixed
2763
+ * @access private
2764
+ */
2765
+ function _get_lstat_cache_prop($path, $prop)
2766
+ {
2767
+ return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
2768
+ }
2769
+
2770
+ /**
2771
+ * Return a stat or lstat properity
2772
+ *
2773
+ * Uses cache if appropriate.
2774
+ *
2775
+ * @param string $path
2776
+ * @param string $prop
2777
+ * @return mixed
2778
+ * @access private
2779
+ */
2780
+ function _get_xstat_cache_prop($path, $prop, $type)
2781
+ {
2782
+ if ($this->use_stat_cache) {
2783
+ $path = $this->_realpath($path);
2784
+
2785
+ $result = $this->_query_stat_cache($path);
2786
+
2787
+ if (is_object($result) && isset($result->$type)) {
2788
+ return $result->{$type}[$prop];
2789
+ }
2790
+ }
2791
+
2792
+ $result = $this->$type($path);
2793
+
2794
+ if ($result === false || !isset($result[$prop])) {
2795
+ return false;
2796
+ }
2797
+
2798
+ return $result[$prop];
2799
+ }
2800
+
2801
+ /**
2802
+ * Renames a file or a directory on the SFTP server
2803
+ *
2804
+ * @param string $oldname
2805
+ * @param string $newname
2806
+ * @return bool
2807
+ * @access public
2808
+ */
2809
+ function rename($oldname, $newname)
2810
+ {
2811
+ if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
2812
+ return false;
2813
+ }
2814
+
2815
+ $oldname = $this->_realpath($oldname);
2816
+ $newname = $this->_realpath($newname);
2817
+ if ($oldname === false || $newname === false) {
2818
+ return false;
2819
+ }
2820
+
2821
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
2822
+ $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
2823
+ if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
2824
+ return false;
2825
+ }
2826
+
2827
+ $response = $this->_get_sftp_packet();
2828
+ if ($this->packet_type != NET_SFTP_STATUS) {
2829
+ user_error('Expected SSH_FXP_STATUS');
2830
+ return false;
2831
+ }
2832
+
2833
+ // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
2834
+ if (strlen($response) < 4) {
2835
+ return false;
2836
+ }
2837
+ extract(unpack('Nstatus', $this->_string_shift($response, 4)));
2838
+ if ($status != NET_SFTP_STATUS_OK) {
2839
+ $this->_logError($response, $status);
2840
+ return false;
2841
+ }
2842
+
2843
+ // don't move the stat cache entry over since this operation could very well change the
2844
+ // atime and mtime attributes
2845
+ //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
2846
+ $this->_remove_from_stat_cache($oldname);
2847
+ $this->_remove_from_stat_cache($newname);
2848
+
2849
+ return true;
2850
+ }
2851
+
2852
+ /**
2853
+ * Parse Attributes
2854
+ *
2855
+ * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
2856
+ *
2857
+ * @param string $response
2858
+ * @return array
2859
+ * @access private
2860
+ */
2861
+ function _parseAttributes(&$response)
2862
+ {
2863
+ $attr = array();
2864
+ if (strlen($response) < 4) {
2865
+ user_error('Malformed file attributes');
2866
+ return array();
2867
+ }
2868
+ extract(unpack('Nflags', $this->_string_shift($response, 4)));
2869
+ // SFTPv4+ have a type field (a byte) that follows the above flag field
2870
+ foreach ($this->attributes as $key => $value) {
2871
+ switch ($flags & $key) {
2872
+ case NET_SFTP_ATTR_SIZE: // 0x00000001
2873
+ // The size attribute is defined as an unsigned 64-bit integer.
2874
+ // The following will use floats on 32-bit platforms, if necessary.
2875
+ // As can be seen in the BigInteger class, floats are generally
2876
+ // IEEE 754 binary64 "double precision" on such platforms and
2877
+ // as such can represent integers of at least 2^50 without loss
2878
+ // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
2879
+ $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
2880
+ break;
2881
+ case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
2882
+ if (strlen($response) < 8) {
2883
+ user_error('Malformed file attributes');
2884
+ return $attr;
2885
+ }
2886
+ $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
2887
+ break;
2888
+ case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
2889
+ if (strlen($response) < 4) {
2890
+ user_error('Malformed file attributes');
2891
+ return $attr;
2892
+ }
2893
+ $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
2894
+ // mode == permissions; permissions was the original array key and is retained for bc purposes.
2895
+ // mode was added because that's the more industry standard terminology
2896
+ $attr+= array('mode' => $attr['permissions']);
2897
+ $fileType = $this->_parseMode($attr['permissions']);
2898
+ if ($fileType !== false) {
2899
+ $attr+= array('type' => $fileType);
2900
+ }
2901
+ break;
2902
+ case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
2903
+ if (strlen($response) < 8) {
2904
+ user_error('Malformed file attributes');
2905
+ return $attr;
2906
+ }
2907
+ $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
2908
+ break;
2909
+ case NET_SFTP_ATTR_EXTENDED: // 0x80000000
2910
+ if (strlen($response) < 4) {
2911
+ user_error('Malformed file attributes');
2912
+ return $attr;
2913
+ }
2914
+ extract(unpack('Ncount', $this->_string_shift($response, 4)));
2915
+ for ($i = 0; $i < $count; $i++) {
2916
+ if (strlen($response) < 4) {
2917
+ user_error('Malformed file attributes');
2918
+ return $attr;
2919
+ }
2920
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
2921
+ $key = $this->_string_shift($response, $length);
2922
+ if (strlen($response) < 4) {
2923
+ user_error('Malformed file attributes');
2924
+ return $attr;
2925
+ }
2926
+ extract(unpack('Nlength', $this->_string_shift($response, 4)));
2927
+ $attr[$key] = $this->_string_shift($response, $length);
2928
+ }
2929
+ }
2930
+ }
2931
+ return $attr;
2932
+ }
2933
+
2934
+ /**
2935
+ * Attempt to identify the file type
2936
+ *
2937
+ * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
2938
+ *
2939
+ * @param int $mode
2940
+ * @return int
2941
+ * @access private
2942
+ */
2943
+ function _parseMode($mode)
2944
+ {
2945
+ // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
2946
+ // see, also, http://linux.die.net/man/2/stat
2947
+ switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
2948
+ case 0000000: // no file type specified - figure out the file type using alternative means
2949
+ return false;
2950
+ case 0040000:
2951
+ return NET_SFTP_TYPE_DIRECTORY;
2952
+ case 0100000:
2953
+ return NET_SFTP_TYPE_REGULAR;
2954
+ case 0120000:
2955
+ return NET_SFTP_TYPE_SYMLINK;
2956
+ // new types introduced in SFTPv5+
2957
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
2958
+ case 0010000: // named pipe (fifo)
2959
+ return NET_SFTP_TYPE_FIFO;
2960
+ case 0020000: // character special
2961
+ return NET_SFTP_TYPE_CHAR_DEVICE;
2962
+ case 0060000: // block special
2963
+ return NET_SFTP_TYPE_BLOCK_DEVICE;
2964
+ case 0140000: // socket
2965
+ return NET_SFTP_TYPE_SOCKET;
2966
+ case 0160000: // whiteout
2967
+ // "SPECIAL should be used for files that are of
2968
+ // a known type which cannot be expressed in the protocol"
2969
+ return NET_SFTP_TYPE_SPECIAL;
2970
+ default:
2971
+ return NET_SFTP_TYPE_UNKNOWN;
2972
+ }
2973
+ }
2974
+
2975
+ /**
2976
+ * Parse Longname
2977
+ *
2978
+ * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
2979
+ * a file as a directory and see if an error is returned or you could try to parse the
2980
+ * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
2981
+ * The result is returned using the
2982
+ * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
2983
+ *
2984
+ * If the longname is in an unrecognized format bool(false) is returned.
2985
+ *
2986
+ * @param string $longname
2987
+ * @return mixed
2988
+ * @access private
2989
+ */
2990
+ function _parseLongname($longname)
2991
+ {
2992
+ // http://en.wikipedia.org/wiki/Unix_file_types
2993
+ // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
2994
+ if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
2995
+ switch ($longname[0]) {
2996
+ case '-':
2997
+ return NET_SFTP_TYPE_REGULAR;
2998
+ case 'd':
2999
+ return NET_SFTP_TYPE_DIRECTORY;
3000
+ case 'l':
3001
+ return NET_SFTP_TYPE_SYMLINK;
3002
+ default:
3003
+ return NET_SFTP_TYPE_SPECIAL;
3004
+ }
3005
+ }
3006
+
3007
+ return false;
3008
+ }
3009
+
3010
+ /**
3011
+ * Sends SFTP Packets
3012
+ *
3013
+ * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3014
+ *
3015
+ * @param int $type
3016
+ * @param string $data
3017
+ * @see self::_get_sftp_packet()
3018
+ * @see Net_SSH2::_send_channel_packet()
3019
+ * @return bool
3020
+ * @access private
3021
+ */
3022
+ function _send_sftp_packet($type, $data, $request_id = 1)
3023
+ {
3024
+ $packet = $this->use_request_id ?
3025
+ pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
3026
+ pack('NCa*', strlen($data) + 1, $type, $data);
3027
+
3028
+ $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
3029
+ $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
3030
+ $stop = strtok(microtime(), ' ') + strtok('');
3031
+
3032
+ if (defined('NET_SFTP_LOGGING')) {
3033
+ $packet_type = '-> ' . $this->packet_types[$type] .
3034
+ ' (' . round($stop - $start, 4) . 's)';
3035
+ if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
3036
+ echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
3037
+ flush();
3038
+ ob_flush();
3039
+ } else {
3040
+ $this->packet_type_log[] = $packet_type;
3041
+ if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
3042
+ $this->packet_log[] = $data;
3043
+ }
3044
+ }
3045
+ }
3046
+
3047
+ return $result;
3048
+ }
3049
+
3050
+ /**
3051
+ * Resets a connection for re-use
3052
+ *
3053
+ * @param int $reason
3054
+ * @access private
3055
+ */
3056
+ function _reset_connection($reason)
3057
+ {
3058
+ parent::_reset_connection($reason);
3059
+ $this->use_request_id = false;
3060
+ $this->pwd = false;
3061
+ $this->requestBuffer = array();
3062
+ }
3063
+
3064
+ /**
3065
+ * Receives SFTP Packets
3066
+ *
3067
+ * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
3068
+ *
3069
+ * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
3070
+ * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
3071
+ * messages containing one SFTP packet.
3072
+ *
3073
+ * @see self::_send_sftp_packet()
3074
+ * @return string
3075
+ * @access private
3076
+ */
3077
+ function _get_sftp_packet($request_id = null)
3078
+ {
3079
+ if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
3080
+ $this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
3081
+ $temp = $this->requestBuffer[$request_id]['packet'];
3082
+ unset($this->requestBuffer[$request_id]);
3083
+ return $temp;
3084
+ }
3085
+
3086
+ // in SSH2.php the timeout is cumulative per function call. eg. exec() will
3087
+ // timeout after 10s. but for SFTP.php it's cumulative per packet
3088
+ $this->curTimeout = $this->timeout;
3089
+
3090
+ $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
3091
+
3092
+ // SFTP packet length
3093
+ while (strlen($this->packet_buffer) < 4) {
3094
+ $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
3095
+ if (is_bool($temp)) {
3096
+ $this->packet_type = false;
3097
+ $this->packet_buffer = '';
3098
+ return false;
3099
+ }
3100
+ $this->packet_buffer.= $temp;
3101
+ }
3102
+ if (strlen($this->packet_buffer) < 4) {
3103
+ return false;
3104
+ }
3105
+ extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
3106
+ $tempLength = $length;
3107
+ $tempLength-= strlen($this->packet_buffer);
3108
+
3109
+
3110
+ // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
3111
+ if ($tempLength > 256 * 1024) {
3112
+ user_error('Invalid SFTP packet size');
3113
+ return false;
3114
+ }
3115
+
3116
+ // SFTP packet type and data payload
3117
+ while ($tempLength > 0) {
3118
+ $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL, true);
3119
+ if (is_bool($temp)) {
3120
+ $this->packet_type = false;
3121
+ $this->packet_buffer = '';
3122
+ return false;
3123
+ }
3124
+ $this->packet_buffer.= $temp;
3125
+ $tempLength-= strlen($temp);
3126
+ }
3127
+
3128
+ $stop = strtok(microtime(), ' ') + strtok('');
3129
+
3130
+ $this->packet_type = ord($this->_string_shift($this->packet_buffer));
3131
+
3132
+ if ($this->use_request_id) {
3133
+ extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id
3134
+ $length-= 5; // account for the request id and the packet type
3135
+ } else {
3136
+ $length-= 1; // account for the packet type
3137
+ }
3138
+
3139
+ $packet = $this->_string_shift($this->packet_buffer, $length);
3140
+
3141
+ if (defined('NET_SFTP_LOGGING')) {
3142
+ $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
3143
+ ' (' . round($stop - $start, 4) . 's)';
3144
+ if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
3145
+ echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
3146
+ flush();
3147
+ ob_flush();
3148
+ } else {
3149
+ $this->packet_type_log[] = $packet_type;
3150
+ if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
3151
+ $this->packet_log[] = $packet;
3152
+ }
3153
+ }
3154
+ }
3155
+
3156
+ if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
3157
+ $this->requestBuffer[$packet_id] = array(
3158
+ 'packet_type' => $this->packet_type,
3159
+ 'packet' => $packet
3160
+ );
3161
+ return $this->_get_sftp_packet($request_id);
3162
+ }
3163
+
3164
+ return $packet;
3165
+ }
3166
+
3167
+ /**
3168
+ * Returns a log of the packets that have been sent and received.
3169
+ *
3170
+ * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
3171
+ *
3172
+ * @access public
3173
+ * @return string or Array
3174
+ */
3175
+ function getSFTPLog()
3176
+ {
3177
+ if (!defined('NET_SFTP_LOGGING')) {
3178
+ return false;
3179
+ }
3180
+
3181
+ switch (NET_SFTP_LOGGING) {
3182
+ case NET_SFTP_LOG_COMPLEX:
3183
+ return $this->_format_log($this->packet_log, $this->packet_type_log);
3184
+ break;
3185
+ //case NET_SFTP_LOG_SIMPLE:
3186
+ default:
3187
+ return $this->packet_type_log;
3188
+ }
3189
+ }
3190
+
3191
+ /**
3192
+ * Returns all errors
3193
+ *
3194
+ * @return array
3195
+ * @access public
3196
+ */
3197
+ function getSFTPErrors()
3198
+ {
3199
+ return $this->sftp_errors;
3200
+ }
3201
+
3202
+ /**
3203
+ * Returns the last error
3204
+ *
3205
+ * @return string
3206
+ * @access public
3207
+ */
3208
+ function getLastSFTPError()
3209
+ {
3210
+ return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
3211
+ }
3212
+
3213
+ /**
3214
+ * Get supported SFTP versions
3215
+ *
3216
+ * @return array
3217
+ * @access public
3218
+ */
3219
+ function getSupportedVersions()
3220
+ {
3221
+ $temp = array('version' => $this->version);
3222
+ if (isset($this->extensions['versions'])) {
3223
+ $temp['extensions'] = $this->extensions['versions'];
3224
+ }
3225
+ return $temp;
3226
+ }
3227
+
3228
+ /**
3229
+ * Disconnect
3230
+ *
3231
+ * @param int $reason
3232
+ * @return bool
3233
+ * @access private
3234
+ */
3235
+ function _disconnect($reason)
3236
+ {
3237
+ $this->pwd = false;
3238
+ parent::_disconnect($reason);
3239
+ }
3240
+ }
phpseclib/Net/SSH1.php CHANGED
@@ -1169,6 +1169,9 @@ class Net_SSH1
1169
 
1170
  while ($length > 0) {
1171
  $temp = fread($this->fsock, $length);
 
 
 
1172
  $raw.= $temp;
1173
  $length-= strlen($temp);
1174
  }
1169
 
1170
  while ($length > 0) {
1171
  $temp = fread($this->fsock, $length);
1172
+ if (strlen($temp) != $length) {
1173
+ return false;
1174
+ }
1175
  $raw.= $temp;
1176
  $length-= strlen($temp);
1177
  }
phpseclib/Net/SSH2.php CHANGED
@@ -214,6 +214,15 @@ class Net_SSH2
214
  */
215
  var $kex_algorithms = false;
216
 
 
 
 
 
 
 
 
 
 
217
  /**
218
  * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
219
  *
@@ -322,6 +331,15 @@ class Net_SSH2
322
  */
323
  var $languages_client_to_server = false;
324
 
 
 
 
 
 
 
 
 
 
325
  /**
326
  * Block Size for Server to Client Encryption
327
  *
@@ -579,6 +597,20 @@ class Net_SSH2
579
  */
580
  var $window_size = 0x7FFFFFFF;
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  /**
583
  * Window size, server to client
584
  *
@@ -903,14 +935,6 @@ class Net_SSH2
903
  */
904
  var $bad_key_size_fix = false;
905
 
906
- /**
907
- * The selected decryption algorithm
908
- *
909
- * @var string
910
- * @access private
911
- */
912
- var $decrypt_algorithm = '';
913
-
914
  /**
915
  * Should we try to re-connect to re-establish keys?
916
  *
@@ -1228,8 +1252,11 @@ class Net_SSH2
1228
  $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
1229
  $this->curTimeout-= $elapsed;
1230
  }
1231
-
1232
- $temp.= fgets($this->fsock, 255);
 
 
 
1233
  }
1234
 
1235
  if (feof($this->fsock)) {
@@ -1324,137 +1351,59 @@ class Net_SSH2
1324
  */
1325
  function _key_exchange($kexinit_payload_server = false)
1326
  {
1327
- static $kex_algorithms = array(
1328
- 'diffie-hellman-group1-sha1', // REQUIRED
1329
- 'diffie-hellman-group14-sha1', // REQUIRED
1330
- 'diffie-hellman-group-exchange-sha1', // RFC 4419
1331
- 'diffie-hellman-group-exchange-sha256', // RFC 4419
1332
- );
1333
-
1334
- static $server_host_key_algorithms = array(
1335
- 'rsa-sha2-256', // RFC 8332
1336
- 'rsa-sha2-512', // RFC 8332
1337
- 'ssh-rsa', // RECOMMENDED sign Raw RSA Key
1338
- 'ssh-dss' // REQUIRED sign Raw DSS Key
1339
- );
1340
-
1341
- static $encryption_algorithms = false;
1342
- if ($encryption_algorithms === false) {
1343
- $encryption_algorithms = array(
1344
- // from <http://tools.ietf.org/html/rfc4345#section-4>:
1345
- 'arcfour256',
1346
- 'arcfour128',
1347
-
1348
- //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
1349
-
1350
- // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
1351
- 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
1352
- 'aes192-ctr', // RECOMMENDED AES with 192-bit key
1353
- 'aes256-ctr', // RECOMMENDED AES with 256-bit key
1354
-
1355
- 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
1356
- 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
1357
- 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
1358
-
1359
- 'aes128-cbc', // RECOMMENDED AES with a 128-bit key
1360
- 'aes192-cbc', // OPTIONAL AES with a 192-bit key
1361
- 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
1362
-
1363
- 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key
1364
- 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key
1365
- 'twofish256-cbc',
1366
- 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc"
1367
- // (this is being retained for historical reasons)
1368
-
1369
- 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode
1370
-
1371
- 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode
1372
-
1373
- '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
1374
-
1375
- '3des-cbc', // REQUIRED three-key 3DES in CBC mode
1376
- //'none' // OPTIONAL no encryption; NOT RECOMMENDED
1377
- );
1378
-
1379
- if (extension_loaded('openssl') && !extension_loaded('mcrypt')) {
1380
- // OpenSSL does not support arcfour256 in any capacity and arcfour128 / arcfour support is limited to
1381
- // instances that do not use continuous buffers
1382
- $encryption_algorithms = array_diff(
1383
- $encryption_algorithms,
1384
- array('arcfour256', 'arcfour128', 'arcfour')
1385
- );
1386
- }
1387
-
1388
- if (phpseclib_resolve_include_path('Crypt/RC4.php') === false) {
1389
- $encryption_algorithms = array_diff(
1390
- $encryption_algorithms,
1391
- array('arcfour256', 'arcfour128', 'arcfour')
1392
- );
1393
- }
1394
- if (phpseclib_resolve_include_path('Crypt/Rijndael.php') === false) {
1395
- $encryption_algorithms = array_diff(
1396
- $encryption_algorithms,
1397
- array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc')
1398
- );
1399
- }
1400
- if (phpseclib_resolve_include_path('Crypt/Twofish.php') === false) {
1401
- $encryption_algorithms = array_diff(
1402
- $encryption_algorithms,
1403
- array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc')
1404
- );
1405
- }
1406
- if (phpseclib_resolve_include_path('Crypt/Blowfish.php') === false) {
1407
- $encryption_algorithms = array_diff(
1408
- $encryption_algorithms,
1409
- array('blowfish-ctr', 'blowfish-cbc')
1410
- );
1411
- }
1412
- if (phpseclib_resolve_include_path('Crypt/TripleDES.php') === false) {
1413
- $encryption_algorithms = array_diff(
1414
- $encryption_algorithms,
1415
- array('3des-ctr', '3des-cbc')
1416
- );
1417
- }
1418
- $encryption_algorithms = array_values($encryption_algorithms);
1419
- }
1420
-
1421
- $mac_algorithms = array(
1422
- // from <http://www.ietf.org/rfc/rfc6668.txt>:
1423
- 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
1424
-
1425
- 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
1426
- 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
1427
- 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
1428
- 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
1429
- //'none' // OPTIONAL no MAC; NOT RECOMMENDED
1430
- );
1431
-
1432
- static $compression_algorithms = array(
1433
- 'none' // REQUIRED no compression
1434
- //'zlib' // OPTIONAL ZLIB (LZ77) compression
1435
- );
1436
 
1437
  // some SSH servers have buggy implementations of some of the above algorithms
1438
  switch (true) {
1439
  case $this->server_identifier == 'SSH-2.0-SSHD':
1440
  case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK':
1441
- $mac_algorithms = array_values(array_diff(
1442
- $mac_algorithms,
1443
- array('hmac-sha1-96', 'hmac-md5-96')
1444
- ));
 
 
 
 
 
 
 
 
1445
  }
1446
 
1447
- static $str_kex_algorithms, $str_server_host_key_algorithms,
1448
- $encryption_algorithms_server_to_client, $mac_algorithms_server_to_client, $compression_algorithms_server_to_client,
1449
- $encryption_algorithms_client_to_server, $mac_algorithms_client_to_server, $compression_algorithms_client_to_server;
1450
-
1451
- if (empty($str_kex_algorithms)) {
1452
- $str_kex_algorithms = implode(',', $kex_algorithms);
1453
- $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
1454
- $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms);
1455
- $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms);
1456
- $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);
1457
- }
1458
 
1459
  $client_cookie = crypt_random_string(16);
1460
 
@@ -1581,14 +1530,14 @@ class Net_SSH2
1581
  // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
1582
  // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
1583
  // diffie-hellman key exchange as fast as possible
1584
- $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client);
1585
  $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
1586
  if ($decryptKeyLength === null) {
1587
  user_error('No compatible server to client encryption algorithms found');
1588
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1589
  }
1590
 
1591
- $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server);
1592
  $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
1593
  if ($encryptKeyLength === null) {
1594
  user_error('No compatible client to server encryption algorithms found');
@@ -1598,7 +1547,7 @@ class Net_SSH2
1598
  $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength;
1599
 
1600
  // through diffie-hellman key exchange a symmetric key is obtained
1601
- $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
1602
  if ($kex_algorithm === false) {
1603
  user_error('No compatible key exchange algorithms found');
1604
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
@@ -1618,6 +1567,7 @@ class Net_SSH2
1618
  if (!$this->_send_binary_packet($packet)) {
1619
  return false;
1620
  }
 
1621
 
1622
  $response = $this->_get_binary_packet();
1623
  if ($response === false) {
@@ -1633,6 +1583,7 @@ class Net_SSH2
1633
  user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
1634
  return false;
1635
  }
 
1636
 
1637
  if (strlen($response) < 4) {
1638
  return false;
@@ -1721,6 +1672,9 @@ class Net_SSH2
1721
  user_error('Connection closed by server');
1722
  return false;
1723
  }
 
 
 
1724
 
1725
  $response = $this->_get_binary_packet();
1726
  if ($response === false) {
@@ -1734,9 +1688,15 @@ class Net_SSH2
1734
  extract(unpack('Ctype', $this->_string_shift($response, 1)));
1735
 
1736
  if ($type != $serverKexReplyMessage) {
1737
- user_error('Expected SSH_MSG_KEXDH_REPLY');
 
 
 
1738
  return false;
1739
  }
 
 
 
1740
 
1741
  if (strlen($response) < 4) {
1742
  return false;
@@ -1853,161 +1813,8 @@ class Net_SSH2
1853
  return false;
1854
  }
1855
 
1856
- switch ($encrypt) {
1857
- case '3des-cbc':
1858
- if (!class_exists('Crypt_TripleDES')) {
1859
- include_once 'Crypt/TripleDES.php';
1860
- }
1861
- $this->encrypt = new Crypt_TripleDES();
1862
- // $this->encrypt_block_size = 64 / 8 == the default
1863
- break;
1864
- case '3des-ctr':
1865
- if (!class_exists('Crypt_TripleDES')) {
1866
- include_once 'Crypt/TripleDES.php';
1867
- }
1868
- $this->encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
1869
- // $this->encrypt_block_size = 64 / 8 == the default
1870
- break;
1871
- case 'aes256-cbc':
1872
- case 'aes192-cbc':
1873
- case 'aes128-cbc':
1874
- if (!class_exists('Crypt_Rijndael')) {
1875
- include_once 'Crypt/Rijndael.php';
1876
- }
1877
- $this->encrypt = new Crypt_Rijndael();
1878
- $this->encrypt_block_size = 16; // eg. 128 / 8
1879
- break;
1880
- case 'aes256-ctr':
1881
- case 'aes192-ctr':
1882
- case 'aes128-ctr':
1883
- if (!class_exists('Crypt_Rijndael')) {
1884
- include_once 'Crypt/Rijndael.php';
1885
- }
1886
- $this->encrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR);
1887
- $this->encrypt_block_size = 16; // eg. 128 / 8
1888
- break;
1889
- case 'blowfish-cbc':
1890
- if (!class_exists('Crypt_Blowfish')) {
1891
- include_once 'Crypt/Blowfish.php';
1892
- }
1893
- $this->encrypt = new Crypt_Blowfish();
1894
- $this->encrypt_block_size = 8;
1895
- break;
1896
- case 'blowfish-ctr':
1897
- if (!class_exists('Crypt_Blowfish')) {
1898
- include_once 'Crypt/Blowfish.php';
1899
- }
1900
- $this->encrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
1901
- $this->encrypt_block_size = 8;
1902
- break;
1903
- case 'twofish128-cbc':
1904
- case 'twofish192-cbc':
1905
- case 'twofish256-cbc':
1906
- case 'twofish-cbc':
1907
- if (!class_exists('Crypt_Twofish')) {
1908
- include_once 'Crypt/Twofish.php';
1909
- }
1910
- $this->encrypt = new Crypt_Twofish();
1911
- $this->encrypt_block_size = 16;
1912
- break;
1913
- case 'twofish128-ctr':
1914
- case 'twofish192-ctr':
1915
- case 'twofish256-ctr':
1916
- if (!class_exists('Crypt_Twofish')) {
1917
- include_once 'Crypt/Twofish.php';
1918
- }
1919
- $this->encrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
1920
- $this->encrypt_block_size = 16;
1921
- break;
1922
- case 'arcfour':
1923
- case 'arcfour128':
1924
- case 'arcfour256':
1925
- if (!class_exists('Crypt_RC4')) {
1926
- include_once 'Crypt/RC4.php';
1927
- }
1928
- $this->encrypt = new Crypt_RC4();
1929
- break;
1930
- case 'none':
1931
- //$this->encrypt = new Crypt_Null();
1932
- }
1933
-
1934
- switch ($decrypt) {
1935
- case '3des-cbc':
1936
- if (!class_exists('Crypt_TripleDES')) {
1937
- include_once 'Crypt/TripleDES.php';
1938
- }
1939
- $this->decrypt = new Crypt_TripleDES();
1940
- break;
1941
- case '3des-ctr':
1942
- if (!class_exists('Crypt_TripleDES')) {
1943
- include_once 'Crypt/TripleDES.php';
1944
- }
1945
- $this->decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
1946
- break;
1947
- case 'aes256-cbc':
1948
- case 'aes192-cbc':
1949
- case 'aes128-cbc':
1950
- if (!class_exists('Crypt_Rijndael')) {
1951
- include_once 'Crypt/Rijndael.php';
1952
- }
1953
- $this->decrypt = new Crypt_Rijndael();
1954
- $this->decrypt_block_size = 16;
1955
- break;
1956
- case 'aes256-ctr':
1957
- case 'aes192-ctr':
1958
- case 'aes128-ctr':
1959
- if (!class_exists('Crypt_Rijndael')) {
1960
- include_once 'Crypt/Rijndael.php';
1961
- }
1962
- $this->decrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR);
1963
- $this->decrypt_block_size = 16;
1964
- break;
1965
- case 'blowfish-cbc':
1966
- if (!class_exists('Crypt_Blowfish')) {
1967
- include_once 'Crypt/Blowfish.php';
1968
- }
1969
- $this->decrypt = new Crypt_Blowfish();
1970
- $this->decrypt_block_size = 8;
1971
- break;
1972
- case 'blowfish-ctr':
1973
- if (!class_exists('Crypt_Blowfish')) {
1974
- include_once 'Crypt/Blowfish.php';
1975
- }
1976
- $this->decrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
1977
- $this->decrypt_block_size = 8;
1978
- break;
1979
- case 'twofish128-cbc':
1980
- case 'twofish192-cbc':
1981
- case 'twofish256-cbc':
1982
- case 'twofish-cbc':
1983
- if (!class_exists('Crypt_Twofish')) {
1984
- include_once 'Crypt/Twofish.php';
1985
- }
1986
- $this->decrypt = new Crypt_Twofish();
1987
- $this->decrypt_block_size = 16;
1988
- break;
1989
- case 'twofish128-ctr':
1990
- case 'twofish192-ctr':
1991
- case 'twofish256-ctr':
1992
- if (!class_exists('Crypt_Twofish')) {
1993
- include_once 'Crypt/Twofish.php';
1994
- }
1995
- $this->decrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
1996
- $this->decrypt_block_size = 16;
1997
- break;
1998
- case 'arcfour':
1999
- case 'arcfour128':
2000
- case 'arcfour256':
2001
- if (!class_exists('Crypt_RC4')) {
2002
- include_once 'Crypt/RC4.php';
2003
- }
2004
- $this->decrypt = new Crypt_RC4();
2005
- break;
2006
- case 'none':
2007
- //$this->decrypt = new Crypt_Null();
2008
- }
2009
-
2010
- $this->decrypt_algorithm = $decrypt;
2011
 
2012
  $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
2013
 
@@ -2018,6 +1825,10 @@ class Net_SSH2
2018
  $this->encrypt->enableContinuousBuffer();
2019
  $this->encrypt->disablePadding();
2020
 
 
 
 
 
2021
  $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
2022
  while ($this->encrypt_block_size > strlen($iv)) {
2023
  $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
@@ -2029,6 +1840,8 @@ class Net_SSH2
2029
  $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2030
  }
2031
  $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
 
 
2032
  }
2033
 
2034
  if ($this->decrypt) {
@@ -2038,6 +1851,10 @@ class Net_SSH2
2038
  $this->decrypt->enableContinuousBuffer();
2039
  $this->decrypt->disablePadding();
2040
 
 
 
 
 
2041
  $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
2042
  while ($this->decrypt_block_size > strlen($iv)) {
2043
  $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
@@ -2049,6 +1866,8 @@ class Net_SSH2
2049
  $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2050
  }
2051
  $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
 
 
2052
  }
2053
 
2054
  /* The "arcfour128" algorithm is the RC4 cipher, as described in
@@ -2065,7 +1884,7 @@ class Net_SSH2
2065
  $this->decrypt->decrypt(str_repeat("\0", 1536));
2066
  }
2067
 
2068
- $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server);
2069
  if ($mac_algorithm === false) {
2070
  user_error('No compatible client to server message authentication algorithms found');
2071
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
@@ -2093,8 +1912,9 @@ class Net_SSH2
2093
  $this->hmac_create = new Crypt_Hash('md5-96');
2094
  $createKeyLength = 16;
2095
  }
 
2096
 
2097
- $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client);
2098
  if ($mac_algorithm === false) {
2099
  user_error('No compatible server to client message authentication algorithms found');
2100
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
@@ -2128,6 +1948,7 @@ class Net_SSH2
2128
  $checkKeyLength = 16;
2129
  $this->hmac_size = 12;
2130
  }
 
2131
 
2132
  $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
2133
  while ($createKeyLength > strlen($key)) {
@@ -2141,19 +1962,19 @@ class Net_SSH2
2141
  }
2142
  $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
2143
 
2144
- $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client);
2145
  if ($compression_algorithm === false) {
2146
- user_error('No compatible server to client compression algorithms found');
2147
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
2148
  }
2149
- $this->decompress = $compression_algorithm == 'zlib';
2150
 
2151
- $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server);
2152
  if ($compression_algorithm === false) {
2153
- user_error('No compatible client to server compression algorithms found');
2154
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
2155
  }
2156
- $this->compress = $compression_algorithm == 'zlib';
2157
 
2158
  return true;
2159
  }
@@ -2201,6 +2022,79 @@ class Net_SSH2
2201
  return null;
2202
  }
2203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2204
  /**
2205
  * Tests whether or not proposed algorithm has a potential for issues
2206
  *
@@ -2238,6 +2132,15 @@ class Net_SSH2
2238
  {
2239
  $args = func_get_args();
2240
  $this->auth[] = $args;
 
 
 
 
 
 
 
 
 
2241
  return call_user_func_array(array(&$this, '_login'), $args);
2242
  }
2243
 
@@ -2437,9 +2340,7 @@ class Net_SSH2
2437
 
2438
  switch ($type) {
2439
  case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
2440
- if (defined('NET_SSH2_LOGGING')) {
2441
- $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';
2442
- }
2443
  if (strlen($response) < 4) {
2444
  return false;
2445
  }
@@ -2590,12 +2491,8 @@ class Net_SSH2
2590
  // see http://tools.ietf.org/html/rfc4256#section-3.2
2591
  if (strlen($this->last_interactive_response)) {
2592
  $this->last_interactive_response = '';
2593
- } elseif (defined('NET_SSH2_LOGGING')) {
2594
- $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2595
- 'UNKNOWN',
2596
- 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
2597
- $this->message_number_log[count($this->message_number_log) - 1]
2598
- );
2599
  }
2600
 
2601
  if (!count($responses) && $num_prompts) {
@@ -2618,13 +2515,7 @@ class Net_SSH2
2618
  return false;
2619
  }
2620
 
2621
- if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {
2622
- $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2623
- 'UNKNOWN',
2624
- 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE',
2625
- $this->message_number_log[count($this->message_number_log) - 1]
2626
- );
2627
- }
2628
 
2629
  /*
2630
  After receiving the response, the server MUST send either an
@@ -2751,13 +2642,7 @@ class Net_SSH2
2751
  case NET_SSH2_MSG_USERAUTH_PK_OK:
2752
  // we'll just take it on faith that the public key blob and the public key algorithm name are as
2753
  // they should be
2754
- if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {
2755
- $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
2756
- 'UNKNOWN',
2757
- 'NET_SSH2_MSG_USERAUTH_PK_OK',
2758
- $this->message_number_log[count($this->message_number_log) - 1]
2759
- );
2760
- }
2761
  }
2762
 
2763
  $packet = $part1 . chr(1) . $part2;
@@ -3134,7 +3019,7 @@ class Net_SSH2
3134
  * @see self::write()
3135
  * @param string $expect
3136
  * @param int $mode
3137
- * @return string
3138
  * @access public
3139
  */
3140
  function read($expect = '', $mode = NET_SSH2_READ_SIMPLE)
@@ -3367,6 +3252,9 @@ class Net_SSH2
3367
  function ping()
3368
  {
3369
  if (!$this->isAuthenticated()) {
 
 
 
3370
  return false;
3371
  }
3372
 
@@ -3477,7 +3365,7 @@ class Net_SSH2
3477
  // "implementations SHOULD check that the packet length is reasonable"
3478
  // PuTTY uses 0x9000 as the actual max packet size and so to shall we
3479
  if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
3480
- if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt_algorithm) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) {
3481
  $this->bad_key_size_fix = true;
3482
  $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
3483
  return false;
@@ -3815,7 +3703,9 @@ class Net_SSH2
3815
  // on windows this returns a "Warning: Invalid CRT parameters detected" error
3816
  if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
3817
  $this->is_timeout = true;
3818
- $this->_close_channel($client_channel);
 
 
3819
  return true;
3820
  }
3821
  $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
@@ -3853,11 +3743,13 @@ class Net_SSH2
3853
 
3854
  // resize the window, if appropriate
3855
  if ($this->window_size_server_to_client[$channel] < 0) {
3856
- $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size);
 
 
3857
  if (!$this->_send_binary_packet($packet)) {
3858
  return false;
3859
  }
3860
- $this->window_size_server_to_client[$channel]+= $this->window_size;
3861
  }
3862
 
3863
  switch ($type) {
@@ -4019,7 +3911,7 @@ class Net_SSH2
4019
  $this->channel_buffers[$channel][] = $data;
4020
  break;
4021
  case NET_SSH2_MSG_CHANNEL_CLOSE:
4022
- $this->curTimeout = 0;
4023
 
4024
  if ($this->bitmap & NET_SSH2_MASK_SHELL) {
4025
  $this->bitmap&= ~NET_SSH2_MASK_SHELL;
@@ -4248,11 +4140,15 @@ class Net_SSH2
4248
 
4249
  $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4250
 
4251
- $this->curTimeout = 0;
4252
 
4253
  while (!is_bool($this->_get_channel_packet($client_channel))) {
4254
  }
4255
 
 
 
 
 
4256
  if ($want_reply) {
4257
  $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4258
  }
@@ -4274,10 +4170,14 @@ class Net_SSH2
4274
  if ($this->bitmap & NET_SSH2_MASK_CONNECTED) {
4275
  $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
4276
  $this->_send_binary_packet($data);
4277
- $this->bitmap = 0;
 
 
 
4278
  fclose($this->fsock);
4279
- return false;
4280
  }
 
 
4281
  }
4282
 
4283
  /**
@@ -4598,6 +4498,294 @@ class Net_SSH2
4598
  return $this->languages_client_to_server;
4599
  }
4600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4601
  /**
4602
  * Returns the banner message.
4603
  *
@@ -4894,4 +5082,22 @@ class Net_SSH2
4894
  $this->windowColumns = $columns;
4895
  $this->windowRows = $rows;
4896
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4897
  }
214
  */
215
  var $kex_algorithms = false;
216
 
217
+ /**
218
+ * Key Exchange Algorithm
219
+ *
220
+ * @see self::getMethodsNegotiated()
221
+ * @var string|false
222
+ * @access private
223
+ */
224
+ var $kex_algorithm = false;
225
+
226
  /**
227
  * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
228
  *
331
  */
332
  var $languages_client_to_server = false;
333
 
334
+ /**
335
+ * Preferred Algorithms
336
+ *
337
+ * @see self::setPreferredAlgorithms()
338
+ * @var array
339
+ * @access private
340
+ */
341
+ var $preferred = array();
342
+
343
  /**
344
  * Block Size for Server to Client Encryption
345
  *
597
  */
598
  var $window_size = 0x7FFFFFFF;
599
 
600
+ /**
601
+ * What we resize the window to
602
+ *
603
+ * When PuTTY resizes the window it doesn't add an additional 0x7FFFFFFF bytes - it adds 0x40000000 bytes.
604
+ * Some SFTP clients (GoAnywhere) don't support adding 0x7FFFFFFF to the window size after the fact so
605
+ * we'll just do what PuTTY does
606
+ *
607
+ * @var int
608
+ * @see self::_send_channel_packet()
609
+ * @see self::exec()
610
+ * @access private
611
+ */
612
+ var $window_resize = 0x40000000;
613
+
614
  /**
615
  * Window size, server to client
616
  *
935
  */
936
  var $bad_key_size_fix = false;
937
 
 
 
 
 
 
 
 
 
938
  /**
939
  * Should we try to re-connect to re-establish keys?
940
  *
1252
  $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
1253
  $this->curTimeout-= $elapsed;
1254
  }
1255
+ $subtemp = fgets($this->fsock, 255);
1256
+ if ($subtemp === '' || $subtemp === false) {
1257
+ return false;
1258
+ }
1259
+ $temp.= $subtemp;
1260
  }
1261
 
1262
  if (feof($this->fsock)) {
1351
  */
1352
  function _key_exchange($kexinit_payload_server = false)
1353
  {
1354
+ $preferred = $this->preferred;
1355
+
1356
+ $kex_algorithms = isset($preferred['kex']) ?
1357
+ $preferred['kex'] :
1358
+ $this->getSupportedKEXAlgorithms();
1359
+ $server_host_key_algorithms = isset($preferred['hostkey']) ?
1360
+ $preferred['hostkey'] :
1361
+ $this->getSupportedHostKeyAlgorithms();
1362
+ $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ?
1363
+ $preferred['server_to_client']['crypt'] :
1364
+ $this->getSupportedEncryptionAlgorithms();
1365
+ $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ?
1366
+ $preferred['client_to_server']['crypt'] :
1367
+ $this->getSupportedEncryptionAlgorithms();
1368
+ $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ?
1369
+ $preferred['server_to_client']['mac'] :
1370
+ $this->getSupportedMACAlgorithms();
1371
+ $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ?
1372
+ $preferred['client_to_server']['mac'] :
1373
+ $this->getSupportedMACAlgorithms();
1374
+ $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ?
1375
+ $preferred['server_to_client']['comp'] :
1376
+ $this->getSupportedCompressionAlgorithms();
1377
+ $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ?
1378
+ $preferred['client_to_server']['comp'] :
1379
+ $this->getSupportedCompressionAlgorithms();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1380
 
1381
  // some SSH servers have buggy implementations of some of the above algorithms
1382
  switch (true) {
1383
  case $this->server_identifier == 'SSH-2.0-SSHD':
1384
  case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK':
1385
+ if (!isset($preferred['server_to_client']['mac'])) {
1386
+ $s2c_mac_algorithms = array_values(array_diff(
1387
+ $s2c_mac_algorithms,
1388
+ array('hmac-sha1-96', 'hmac-md5-96')
1389
+ ));
1390
+ }
1391
+ if (!isset($preferred['client_to_server']['mac'])) {
1392
+ $c2s_mac_algorithms = array_values(array_diff(
1393
+ $c2s_mac_algorithms,
1394
+ array('hmac-sha1-96', 'hmac-md5-96')
1395
+ ));
1396
+ }
1397
  }
1398
 
1399
+ $str_kex_algorithms = implode(',', $kex_algorithms);
1400
+ $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
1401
+ $encryption_algorithms_server_to_client = implode(',', $s2c_encryption_algorithms);
1402
+ $encryption_algorithms_client_to_server = implode(',', $c2s_encryption_algorithms);
1403
+ $mac_algorithms_server_to_client = implode(',', $s2c_mac_algorithms);
1404
+ $mac_algorithms_client_to_server = implode(',', $c2s_mac_algorithms);
1405
+ $compression_algorithms_server_to_client = implode(',', $s2c_compression_algorithms);
1406
+ $compression_algorithms_client_to_server = implode(',', $c2s_compression_algorithms);
 
 
 
1407
 
1408
  $client_cookie = crypt_random_string(16);
1409
 
1530
  // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
1531
  // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
1532
  // diffie-hellman key exchange as fast as possible
1533
+ $decrypt = $this->_array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
1534
  $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
1535
  if ($decryptKeyLength === null) {
1536
  user_error('No compatible server to client encryption algorithms found');
1537
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1538
  }
1539
 
1540
+ $encrypt = $this->_array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
1541
  $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
1542
  if ($encryptKeyLength === null) {
1543
  user_error('No compatible client to server encryption algorithms found');
1547
  $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength;
1548
 
1549
  // through diffie-hellman key exchange a symmetric key is obtained
1550
+ $this->kex_algorithm = $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
1551
  if ($kex_algorithm === false) {
1552
  user_error('No compatible key exchange algorithms found');
1553
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1567
  if (!$this->_send_binary_packet($packet)) {
1568
  return false;
1569
  }
1570
+ $this->_updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
1571
 
1572
  $response = $this->_get_binary_packet();
1573
  if ($response === false) {
1583
  user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
1584
  return false;
1585
  }
1586
+ $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
1587
 
1588
  if (strlen($response) < 4) {
1589
  return false;
1672
  user_error('Connection closed by server');
1673
  return false;
1674
  }
1675
+ if ($clientKexInitMessage == NET_SSH2_MSG_KEXDH_GEX_INIT) {
1676
+ $this->_updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
1677
+ }
1678
 
1679
  $response = $this->_get_binary_packet();
1680
  if ($response === false) {
1688
  extract(unpack('Ctype', $this->_string_shift($response, 1)));
1689
 
1690
  if ($type != $serverKexReplyMessage) {
1691
+ $expected = $serverKexReplyMessage == NET_SSH2_MSG_KEXDH_GEX_REPLY ?
1692
+ 'SSH_MSG_KEXDH_GEX_REPLY' :
1693
+ 'SSH_MSG_KEXDH_REPLY';
1694
+ user_error("Expected $expected");
1695
  return false;
1696
  }
1697
+ if ($serverKexReplyMessage == NET_SSH2_MSG_KEXDH_GEX_REPLY) {
1698
+ $this->_updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
1699
+ }
1700
 
1701
  if (strlen($response) < 4) {
1702
  return false;
1813
  return false;
1814
  }
1815
 
1816
+ $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
1817
+ $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1818
 
1819
  $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
1820
 
1825
  $this->encrypt->enableContinuousBuffer();
1826
  $this->encrypt->disablePadding();
1827
 
1828
+ if ($this->encrypt->getBlockLength()) {
1829
+ $this->encrypt_block_size = $this->encrypt->getBlockLength() >> 3;
1830
+ }
1831
+
1832
  $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1833
  while ($this->encrypt_block_size > strlen($iv)) {
1834
  $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1840
  $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1841
  }
1842
  $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
1843
+
1844
+ $this->encrypt->name = $decrypt;
1845
  }
1846
 
1847
  if ($this->decrypt) {
1851
  $this->decrypt->enableContinuousBuffer();
1852
  $this->decrypt->disablePadding();
1853
 
1854
+ if ($this->decrypt->getBlockLength()) {
1855
+ $this->decrypt_block_size = $this->decrypt->getBlockLength() >> 3;
1856
+ }
1857
+
1858
  $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
1859
  while ($this->decrypt_block_size > strlen($iv)) {
1860
  $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1866
  $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1867
  }
1868
  $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
1869
+
1870
+ $this->decrypt->name = $decrypt;
1871
  }
1872
 
1873
  /* The "arcfour128" algorithm is the RC4 cipher, as described in
1884
  $this->decrypt->decrypt(str_repeat("\0", 1536));
1885
  }
1886
 
1887
+ $mac_algorithm = $this->_array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
1888
  if ($mac_algorithm === false) {
1889
  user_error('No compatible client to server message authentication algorithms found');
1890
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1912
  $this->hmac_create = new Crypt_Hash('md5-96');
1913
  $createKeyLength = 16;
1914
  }
1915
+ $this->hmac_create->name = $mac_algorithm;
1916
 
1917
+ $mac_algorithm = $this->_array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
1918
  if ($mac_algorithm === false) {
1919
  user_error('No compatible server to client message authentication algorithms found');
1920
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1948
  $checkKeyLength = 16;
1949
  $this->hmac_size = 12;
1950
  }
1951
+ $this->hmac_check->name = $mac_algorithm;
1952
 
1953
  $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
1954
  while ($createKeyLength > strlen($key)) {
1962
  }
1963
  $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
1964
 
1965
+ $compression_algorithm = $this->_array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
1966
  if ($compression_algorithm === false) {
1967
+ user_error('No compatible client to server compression algorithms found');
1968
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1969
  }
1970
+ //$this->decompress = $compression_algorithm == 'zlib';
1971
 
1972
+ $compression_algorithm = $this->_array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_client_to_server);
1973
  if ($compression_algorithm === false) {
1974
+ user_error('No compatible server to client compression algorithms found');
1975
  return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1976
  }
1977
+ //$this->compress = $compression_algorithm == 'zlib';
1978
 
1979
  return true;
1980
  }
2022
  return null;
2023
  }
2024
 
2025
+ /**
2026
+ * Maps an encryption algorithm name to an instance of a subclass of
2027
+ * \phpseclib\Crypt\Base.
2028
+ *
2029
+ * @param string $algorithm Name of the encryption algorithm
2030
+ * @return mixed Instance of \phpseclib\Crypt\Base or null for unknown
2031
+ * @access private
2032
+ */
2033
+ function _encryption_algorithm_to_crypt_instance($algorithm)
2034
+ {
2035
+ switch ($algorithm) {
2036
+ case '3des-cbc':
2037
+ if (!class_exists('Crypt_TripleDES')) {
2038
+ include_once 'Crypt/TripleDES.php';
2039
+ }
2040
+ return new Crypt_TripleDES();
2041
+ case '3des-ctr':
2042
+ if (!class_exists('Crypt_TripleDES')) {
2043
+ include_once 'Crypt/TripleDES.php';
2044
+ }
2045
+ return new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
2046
+ case 'aes256-cbc':
2047
+ case 'aes192-cbc':
2048
+ case 'aes128-cbc':
2049
+ if (!class_exists('Crypt_Rijndael')) {
2050
+ include_once 'Crypt/Rijndael.php';
2051
+ }
2052
+ return new Crypt_Rijndael();
2053
+ case 'aes256-ctr':
2054
+ case 'aes192-ctr':
2055
+ case 'aes128-ctr':
2056
+ if (!class_exists('Crypt_Rijndael')) {
2057
+ include_once 'Crypt/Rijndael.php';
2058
+ }
2059
+ return new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR);
2060
+ case 'blowfish-cbc':
2061
+ if (!class_exists('Crypt_Blowfish')) {
2062
+ include_once 'Crypt/Blowfish.php';
2063
+ }
2064
+ return new Crypt_Blowfish();
2065
+ case 'blowfish-ctr':
2066
+ if (!class_exists('Crypt_Blowfish')) {
2067
+ include_once 'Crypt/Blowfish.php';
2068
+ }
2069
+ return new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
2070
+ case 'twofish128-cbc':
2071
+ case 'twofish192-cbc':
2072
+ case 'twofish256-cbc':
2073
+ case 'twofish-cbc':
2074
+ if (!class_exists('Crypt_Twofish')) {
2075
+ include_once 'Crypt/Twofish.php';
2076
+ }
2077
+ return new Crypt_Twofish();
2078
+ case 'twofish128-ctr':
2079
+ case 'twofish192-ctr':
2080
+ case 'twofish256-ctr':
2081
+ if (!class_exists('Crypt_Twofish')) {
2082
+ include_once 'Crypt/Twofish.php';
2083
+ }
2084
+ return new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
2085
+ case 'arcfour':
2086
+ case 'arcfour128':
2087
+ case 'arcfour256':
2088
+ if (!class_exists('Crypt_RC4')) {
2089
+ include_once 'Crypt/RC4.php';
2090
+ }
2091
+ return new Crypt_RC4();
2092
+ case 'none':
2093
+ //return new Crypt_Null();
2094
+ }
2095
+ return null;
2096
+ }
2097
+
2098
  /**
2099
  * Tests whether or not proposed algorithm has a potential for issues
2100
  *
2132
  {
2133
  $args = func_get_args();
2134
  $this->auth[] = $args;
2135
+
2136
+ // try logging with 'none' as an authentication method first since that's what
2137
+ // PuTTY does
2138
+ if ($this->_login($username)) {
2139
+ return true;
2140
+ }
2141
+ if (count($args) == 1) {
2142
+ return false;
2143
+ }
2144
  return call_user_func_array(array(&$this, '_login'), $args);
2145
  }
2146
 
2340
 
2341
  switch ($type) {
2342
  case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
2343
+ $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
 
 
2344
  if (strlen($response) < 4) {
2345
  return false;
2346
  }
2491
  // see http://tools.ietf.org/html/rfc4256#section-3.2
2492
  if (strlen($this->last_interactive_response)) {
2493
  $this->last_interactive_response = '';
2494
+ } else {
2495
+ $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
 
 
 
 
2496
  }
2497
 
2498
  if (!count($responses) && $num_prompts) {
2515
  return false;
2516
  }
2517
 
2518
+ $this->_updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
 
 
 
 
 
 
2519
 
2520
  /*
2521
  After receiving the response, the server MUST send either an
2642
  case NET_SSH2_MSG_USERAUTH_PK_OK:
2643
  // we'll just take it on faith that the public key blob and the public key algorithm name are as
2644
  // they should be
2645
+ $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
 
 
 
 
 
 
2646
  }
2647
 
2648
  $packet = $part1 . chr(1) . $part2;
3019
  * @see self::write()
3020
  * @param string $expect
3021
  * @param int $mode
3022
+ * @return string|bool
3023
  * @access public
3024
  */
3025
  function read($expect = '', $mode = NET_SSH2_READ_SIMPLE)
3252
  function ping()
3253
  {
3254
  if (!$this->isAuthenticated()) {
3255
+ if (!empty($this->auth)) {
3256
+ return $this->_reconnect();
3257
+ }
3258
  return false;
3259
  }
3260
 
3365
  // "implementations SHOULD check that the packet length is reasonable"
3366
  // PuTTY uses 0x9000 as the actual max packet size and so to shall we
3367
  if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
3368
+ if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt->name) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) {
3369
  $this->bad_key_size_fix = true;
3370
  $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
3371
  return false;
3703
  // on windows this returns a "Warning: Invalid CRT parameters detected" error
3704
  if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
3705
  $this->is_timeout = true;
3706
+ if ($client_channel == NET_SSH2_CHANNEL_EXEC && !$this->request_pty) {
3707
+ $this->_close_channel($client_channel);
3708
+ }
3709
  return true;
3710
  }
3711
  $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
3743
 
3744
  // resize the window, if appropriate
3745
  if ($this->window_size_server_to_client[$channel] < 0) {
3746
+ // PuTTY does something more analogous to the following:
3747
+ //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
3748
+ $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
3749
  if (!$this->_send_binary_packet($packet)) {
3750
  return false;
3751
  }
3752
+ $this->window_size_server_to_client[$channel]+= $this->window_resize;
3753
  }
3754
 
3755
  switch ($type) {
3911
  $this->channel_buffers[$channel][] = $data;
3912
  break;
3913
  case NET_SSH2_MSG_CHANNEL_CLOSE:
3914
+ $this->curTimeout = 5;
3915
 
3916
  if ($this->bitmap & NET_SSH2_MASK_SHELL) {
3917
  $this->bitmap&= ~NET_SSH2_MASK_SHELL;
4140
 
4141
  $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4142
 
4143
+ $this->curTimeout = 5;
4144
 
4145
  while (!is_bool($this->_get_channel_packet($client_channel))) {
4146
  }
4147
 
4148
+ if ($this->is_timeout) {
4149
+ $this->disconnect();
4150
+ }
4151
+
4152
  if ($want_reply) {
4153
  $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4154
  }
4170
  if ($this->bitmap & NET_SSH2_MASK_CONNECTED) {
4171
  $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
4172
  $this->_send_binary_packet($data);
4173
+ }
4174
+
4175
+ $this->bitmap = 0;
4176
+ if (is_resource($this->fsock) && get_resource_type($this->fsock) == 'stream') {
4177
  fclose($this->fsock);
 
4178
  }
4179
+
4180
+ return false;
4181
  }
4182
 
4183
  /**
4498
  return $this->languages_client_to_server;
4499
  }
4500
 
4501
+ /**
4502
+ * Returns a list of algorithms the server supports
4503
+ *
4504
+ * @return array
4505
+ * @access public
4506
+ */
4507
+ function getServerAlgorithms()
4508
+ {
4509
+ $this->_connect();
4510
+
4511
+ return array(
4512
+ 'kex' => $this->kex_algorithms,
4513
+ 'hostkey' => $this->server_host_key_algorithms,
4514
+ 'client_to_server' => array(
4515
+ 'crypt' => $this->encryption_algorithms_client_to_server,
4516
+ 'mac' => $this->mac_algorithms_client_to_server,
4517
+ 'comp' => $this->compression_algorithms_client_to_server,
4518
+ 'lang' => $this->languages_client_to_server
4519
+ ),
4520
+ 'server_to_client' => array(
4521
+ 'crypt' => $this->encryption_algorithms_server_to_client,
4522
+ 'mac' => $this->mac_algorithms_server_to_client,
4523
+ 'comp' => $this->compression_algorithms_server_to_client,
4524
+ 'lang' => $this->languages_server_to_client
4525
+ )
4526
+ );
4527
+ }
4528
+
4529
+ /**
4530
+ * Returns a list of KEX algorithms that phpseclib supports
4531
+ *
4532
+ * @return array
4533
+ * @access public
4534
+ */
4535
+ function getSupportedKEXAlgorithms()
4536
+ {
4537
+ $kex_algorithms = array(
4538
+ 'diffie-hellman-group-exchange-sha256',// RFC 4419
4539
+ 'diffie-hellman-group-exchange-sha1', // RFC 4419
4540
+
4541
+ // Diffie-Hellman Key Agreement (DH) using integer modulo prime
4542
+ // groups.
4543
+ 'diffie-hellman-group14-sha1', // REQUIRED
4544
+ 'diffie-hellman-group1-sha1', // REQUIRED
4545
+ );
4546
+
4547
+ return $kex_algorithms;
4548
+ }
4549
+
4550
+ /**
4551
+ * Returns a list of host key algorithms that phpseclib supports
4552
+ *
4553
+ * @return array
4554
+ * @access public
4555
+ */
4556
+ function getSupportedHostKeyAlgorithms()
4557
+ {
4558
+ return array(
4559
+ 'rsa-sha2-256', // RFC 8332
4560
+ 'rsa-sha2-512', // RFC 8332
4561
+ 'ssh-rsa', // RECOMMENDED sign Raw RSA Key
4562
+ 'ssh-dss' // REQUIRED sign Raw DSS Key
4563
+ );
4564
+ }
4565
+
4566
+ /**
4567
+ * Returns a list of symmetric key algorithms that phpseclib supports
4568
+ *
4569
+ * @return array
4570
+ * @access public
4571
+ */
4572
+ function getSupportedEncryptionAlgorithms()
4573
+ {
4574
+ $algos = array(
4575
+ // from <http://tools.ietf.org/html/rfc4345#section-4>:
4576
+ 'arcfour256',
4577
+ 'arcfour128',
4578
+
4579
+ //'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
4580
+
4581
+ // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
4582
+ 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
4583
+ 'aes192-ctr', // RECOMMENDED AES with 192-bit key
4584
+ 'aes256-ctr', // RECOMMENDED AES with 256-bit key
4585
+
4586
+ 'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
4587
+ 'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
4588
+ 'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
4589
+
4590
+ 'aes128-cbc', // RECOMMENDED AES with a 128-bit key
4591
+ 'aes192-cbc', // OPTIONAL AES with a 192-bit key
4592
+ 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
4593
+
4594
+ 'twofish128-cbc', // OPTIONAL Twofish with a 128-bit key
4595
+ 'twofish192-cbc', // OPTIONAL Twofish with a 192-bit key
4596
+ 'twofish256-cbc',
4597
+ 'twofish-cbc', // OPTIONAL alias for "twofish256-cbc"
4598
+ // (this is being retained for historical reasons)
4599
+
4600
+ 'blowfish-ctr', // OPTIONAL Blowfish in SDCTR mode
4601
+
4602
+ 'blowfish-cbc', // OPTIONAL Blowfish in CBC mode
4603
+
4604
+ '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
4605
+
4606
+ '3des-cbc', // REQUIRED three-key 3DES in CBC mode
4607
+
4608
+ //'none' // OPTIONAL no encryption; NOT RECOMMENDED
4609
+ );
4610
+
4611
+ $engines = array(
4612
+ CRYPT_ENGINE_OPENSSL,
4613
+ CRYPT_ENGINE_MCRYPT,
4614
+ CRYPT_ENGINE_INTERNAL
4615
+ );
4616
+
4617
+ $ciphers = array();
4618
+ foreach ($engines as $engine) {
4619
+ foreach ($algos as $algo) {
4620
+ $obj = $this->_encryption_algorithm_to_crypt_instance($algo);
4621
+ if (strtolower(get_class($obj)) == 'crypt_rijndael') {
4622
+ $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo));
4623
+ }
4624
+ switch ($algo) {
4625
+ case 'arcfour128':
4626
+ case 'arcfour256':
4627
+ if ($engine != CRYPT_ENGINE_INTERNAL) {
4628
+ continue 2;
4629
+ }
4630
+ }
4631
+ if ($obj->isValidEngine($engine)) {
4632
+ $algos = array_diff($algos, array($algo));
4633
+ $ciphers[] = $algo;
4634
+ }
4635
+ }
4636
+ }
4637
+
4638
+ return $ciphers;
4639
+ }
4640
+
4641
+ /**
4642
+ * Returns a list of MAC algorithms that phpseclib supports
4643
+ *
4644
+ * @return array
4645
+ * @access public
4646
+ */
4647
+ function getSupportedMACAlgorithms()
4648
+ {
4649
+ return array(
4650
+ // from <http://www.ietf.org/rfc/rfc6668.txt>:
4651
+ 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
4652
+
4653
+ 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
4654
+ 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
4655
+ 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
4656
+ 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
4657
+ //'none' // OPTIONAL no MAC; NOT RECOMMENDED
4658
+ );
4659
+ }
4660
+
4661
+ /**
4662
+ * Returns a list of compression algorithms that phpseclib supports
4663
+ *
4664
+ * @return array
4665
+ * @access public
4666
+ */
4667
+ function getSupportedCompressionAlgorithms()
4668
+ {
4669
+ return array(
4670
+ 'none' // REQUIRED no compression
4671
+ //'zlib' // OPTIONAL ZLIB (LZ77) compression
4672
+ );
4673
+ }
4674
+
4675
+ /**
4676
+ * Return list of negotiated algorithms
4677
+ *
4678
+ * Uses the same format as https://www.php.net/ssh2-methods-negotiated
4679
+ *
4680
+ * @return array
4681
+ * @access public
4682
+ */
4683
+ function getAlgorithmsNegotiated()
4684
+ {
4685
+ $this->_connect();
4686
+
4687
+ return array(
4688
+ 'kex' => $this->kex_algorithm,
4689
+ 'hostkey' => $this->signature_format,
4690
+ 'client_to_server' => array(
4691
+ 'crypt' => $this->encrypt->name,
4692
+ 'mac' => $this->hmac_create->name,
4693
+ 'comp' => 'none',
4694
+ ),
4695
+ 'server_to_client' => array(
4696
+ 'crypt' => $this->decrypt->name,
4697
+ 'mac' => $this->hmac_check->name,
4698
+ 'comp' => 'none',
4699
+ )
4700
+ );
4701
+ }
4702
+
4703
+ /**
4704
+ * Accepts an associative array with up to four parameters as described at
4705
+ * <https://www.php.net/manual/en/function.ssh2-connect.php>
4706
+ *
4707
+ * @param array $methods
4708
+ * @access public
4709
+ */
4710
+ function setPreferredAlgorithms($methods)
4711
+ {
4712
+ $preferred = $methods;
4713
+
4714
+ if (isset($preferred['kex'])) {
4715
+ $preferred['kex'] = array_intersect(
4716
+ $preferred['kex'],
4717
+ $this->getSupportedKEXAlgorithms()
4718
+ );
4719
+ }
4720
+
4721
+ if (isset($preferred['hostkey'])) {
4722
+ $preferred['hostkey'] = array_intersect(
4723
+ $preferred['hostkey'],
4724
+ $this->getSupportedHostKeyAlgorithms()
4725
+ );
4726
+ }
4727
+
4728
+ $keys = array('client_to_server', 'server_to_client');
4729
+ foreach ($keys as $key) {
4730
+ if (isset($preferred[$key])) {
4731
+ $a = &$preferred[$key];
4732
+ if (isset($a['crypt'])) {
4733
+ $a['crypt'] = array_intersect(
4734
+ $a['crypt'],
4735
+ $this->getSupportedEncryptionAlgorithms()
4736
+ );
4737
+ }
4738
+ if (isset($a['comp'])) {
4739
+ $a['comp'] = array_intersect(
4740
+ $a['comp'],
4741
+ $this->getSupportedCompressionAlgorithms()
4742
+ );
4743
+ }
4744
+ if (isset($a['mac'])) {
4745
+ $a['mac'] = array_intersect(
4746
+ $a['mac'],
4747
+ $this->getSupportedMACAlgorithms()
4748
+ );
4749
+ }
4750
+ }
4751
+ }
4752
+
4753
+ $keys = array(
4754
+ 'kex',
4755
+ 'hostkey',
4756
+ 'client_to_server/crypt',
4757
+ 'client_to_server/comp',
4758
+ 'client_to_server/mac',
4759
+ 'server_to_client/crypt',
4760
+ 'server_to_client/comp',
4761
+ 'server_to_client/mac',
4762
+ );
4763
+ foreach ($keys as $key) {
4764
+ $p = $preferred;
4765
+ $m = $methods;
4766
+
4767
+ $subkeys = explode('/', $key);
4768
+ foreach ($subkeys as $subkey) {
4769
+ if (!isset($p[$subkey])) {
4770
+ continue 2;
4771
+ }
4772
+ $p = $p[$subkey];
4773
+ $m = $m[$subkey];
4774
+ }
4775
+
4776
+ if (count($p) != count($m)) {
4777
+ $diff = array_diff($m, $p);
4778
+ $msg = count($diff) == 1 ?
4779
+ ' is not a supported algorithm' :
4780
+ ' are not supported algorithms';
4781
+ user_error(implode(', ', $diff) . $msg);
4782
+ return false;
4783
+ }
4784
+ }
4785
+
4786
+ $this->preferred = $preferred;
4787
+ }
4788
+
4789
  /**
4790
  * Returns the banner message.
4791
  *
5082
  $this->windowColumns = $columns;
5083
  $this->windowRows = $rows;
5084
  }
5085
+
5086
+ /**
5087
+ * Update packet types in log history
5088
+ *
5089
+ * @param string $old
5090
+ * @param string $new
5091
+ * @access private
5092
+ */
5093
+ function _updateLogHistory($old, $new)
5094
+ {
5095
+ if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) {
5096
+ $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
5097
+ $old,
5098
+ $new,
5099
+ $this->message_number_log[count($this->message_number_log) - 1]
5100
+ );
5101
+ }
5102
+ }
5103
  }
phpseclib/System/SSH/Agent.php CHANGED
@@ -263,22 +263,35 @@ class System_SSH_Agent_Identity
263
  $packet = pack('Na*', strlen($packet), $packet);
264
  if (strlen($packet) != fputs($this->fsock, $packet)) {
265
  user_error('Connection closed during signing');
 
266
  }
267
 
268
- $length = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
269
  $type = ord(fread($this->fsock, 1));
270
  if ($type != SYSTEM_SSH_AGENT_SIGN_RESPONSE) {
271
  user_error('Unable to retreive signature');
 
272
  }
273
 
274
  $signature_blob = fread($this->fsock, $length - 1);
 
 
 
 
275
  $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
276
  if ($length != strlen($signature_blob)) {
277
  user_error('Malformed signature blob');
 
278
  }
279
  $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
280
  if ($length > strlen($signature_blob) + 4) {
281
  user_error('Malformed signature blob');
 
282
  }
283
  $type = $this->_string_shift($signature_blob, $length);
284
  $this->_string_shift($signature_blob, 4);
@@ -406,7 +419,12 @@ class System_SSH_Agent
406
  return array();
407
  }
408
 
409
- $length = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
410
  $type = ord(fread($this->fsock, 1));
411
  if ($type != SYSTEM_SSH_AGENT_IDENTITIES_ANSWER) {
412
  user_error('Unable to request identities');
@@ -414,14 +432,38 @@ class System_SSH_Agent
414
  }
415
 
416
  $identities = array();
417
- $keyCount = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
418
  for ($i = 0; $i < $keyCount; $i++) {
419
- $length = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
420
  $key_blob = fread($this->fsock, $length);
 
 
 
 
421
  $key_str = 'ssh-rsa ' . base64_encode($key_blob);
422
- $length = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
423
  if ($length) {
424
- $key_str.= ' ' . fread($this->fsock, $length);
 
 
 
 
 
425
  }
426
  $length = current(unpack('N', substr($key_blob, 0, 4)));
427
  $key_type = substr($key_blob, 4, $length);
@@ -546,14 +588,24 @@ class System_SSH_Agent
546
 
547
  if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
548
  user_error('Connection closed attempting to forward data to SSH agent');
 
549
  }
550
 
551
  $this->socket_buffer = '';
552
  $this->expected_bytes = 0;
553
 
554
- $agent_reply_bytes = current(unpack('N', fread($this->fsock, 4)));
 
 
 
 
 
555
 
556
  $agent_reply_data = fread($this->fsock, $agent_reply_bytes);
 
 
 
 
557
  $agent_reply_data = current(unpack('a*', $agent_reply_data));
558
 
559
  return pack('Na*', $agent_reply_bytes, $agent_reply_data);
263
  $packet = pack('Na*', strlen($packet), $packet);
264
  if (strlen($packet) != fputs($this->fsock, $packet)) {
265
  user_error('Connection closed during signing');
266
+ return false;
267
  }
268
 
269
+ $temp = fread($this->fsock, 4);
270
+ if (strlen($temp) != 4) {
271
+ user_error('Connection closed during signing');
272
+ return false;
273
+ }
274
+ $length = current(unpack('N', $temp));
275
  $type = ord(fread($this->fsock, 1));
276
  if ($type != SYSTEM_SSH_AGENT_SIGN_RESPONSE) {
277
  user_error('Unable to retreive signature');
278
+ return false;
279
  }
280
 
281
  $signature_blob = fread($this->fsock, $length - 1);
282
+ if (strlen($signature_blob) != $length - 1) {
283
+ user_error('Connection closed during signing');
284
+ return false;
285
+ }
286
  $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
287
  if ($length != strlen($signature_blob)) {
288
  user_error('Malformed signature blob');
289
+ return false;
290
  }
291
  $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
292
  if ($length > strlen($signature_blob) + 4) {
293
  user_error('Malformed signature blob');
294
+ return false;
295
  }
296
  $type = $this->_string_shift($signature_blob, $length);
297
  $this->_string_shift($signature_blob, 4);
419
  return array();
420
  }
421
 
422
+ $temp = fread($this->fsock, 4);
423
+ if (strlen($temp) != 4) {
424
+ user_error('Connection closed while requesting identities');
425
+ return array();
426
+ }
427
+ $length = current(unpack('N', $temp));
428
  $type = ord(fread($this->fsock, 1));
429
  if ($type != SYSTEM_SSH_AGENT_IDENTITIES_ANSWER) {
430
  user_error('Unable to request identities');
432
  }
433
 
434
  $identities = array();
435
+ $temp = fread($this->fsock, 4);
436
+ if (strlen($temp) != 4) {
437
+ user_error('Connection closed while requesting identities');
438
+ return array();
439
+ }
440
+ $keyCount = current(unpack('N', $temp));
441
  for ($i = 0; $i < $keyCount; $i++) {
442
+ $temp = fread($this->fsock, 4);
443
+ if (strlen($temp) != 4) {
444
+ user_error('Connection closed while requesting identities');
445
+ return array();
446
+ }
447
+ $length = current(unpack('N', $temp));
448
  $key_blob = fread($this->fsock, $length);
449
+ if (strlen($key_blob) != $length) {
450
+ user_error('Connection closed while requesting identities');
451
+ return array();
452
+ }
453
  $key_str = 'ssh-rsa ' . base64_encode($key_blob);
454
+ $temp = fread($this->fsock, 4);
455
+ if (strlen($temp) != 4) {
456
+ user_error('Connection closed while requesting identities');
457
+ return array();
458
+ }
459
+ $length = current(unpack('N', $temp));
460
  if ($length) {
461
+ $temp = fread($this->fsock, $length);
462
+ if (strlen($temp) != $length) {
463
+ user_error('Connection closed while requesting identities');
464
+ return array();
465
+ }
466
+ $key_str.= ' ' . $temp;
467
  }
468
  $length = current(unpack('N', substr($key_blob, 0, 4)));
469
  $key_type = substr($key_blob, 4, $length);
588
 
589
  if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
590
  user_error('Connection closed attempting to forward data to SSH agent');
591
+ return false;
592
  }
593
 
594
  $this->socket_buffer = '';
595
  $this->expected_bytes = 0;
596
 
597
+ $temp = fread($this->fsock, 4);
598
+ if (strlen($temp) != 4) {
599
+ user_error('Connection closed while reading data response');
600
+ return false;
601
+ }
602
+ $agent_reply_bytes = current(unpack('N', $temp));
603
 
604
  $agent_reply_data = fread($this->fsock, $agent_reply_bytes);
605
+ if (strlen($agent_reply_data) != $agent_reply_bytes) {
606
+ user_error('Connection closed while reading data response');
607
+ return false;
608
+ }
609
  $agent_reply_data = current(unpack('a*', $agent_reply_data));
610
 
611
  return pack('Na*', $agent_reply_bytes, $agent_reply_data);
readme.txt CHANGED
@@ -3,10 +3,10 @@ Contributors: DavidAnderson, TerraFrost, pmbaldha
3
  Donate link: https://sourceforge.net/donate/index.php?group_id=198487
4
  Tags: ssh, sftp
5
  Requires at least: 3.1
6
- Tested up to: 5.6
7
- Stable tag: 0.8.3
8
 
9
- "SSH SFTP Updater Support" is the easiest way to keep your Wordpress installation up-to-date with SFTP.
10
 
11
  == Description ==
12
 
@@ -23,8 +23,6 @@ This plugin is offered and maintained as a free service to the WP community. You
23
  * **[UpdraftPlus](https://updraftplus.com/?ref=212&source=sshsmtp)** simplifies backups and restoration. It is the #1 most-used backup/restore plugin, with over a million currently-active installs.
24
  * **[UpdraftCentral](https://updraftplus.com/updraftcentral/?ref=212&source=sshsmtp)** - a highly efficient way to manage, optimize, update and backup multiple websites from one place.
25
  * **[WP-Optimize](https://getwpo.com/)** helps you to optimize and clean your WordPress database so that it runs at maximum efficiency.
26
- * **[Keyy](https://getkeyy.com/)**: Simple and secure logins with a wave of your phone.
27
- * **[MetaSlider](https://www.metaslider.com)**: Easily create beautifully-designed sliders with the #1-most installed WP slider plugin.
28
  * **More quality plugins**: **[Premium WooCommerce extensions](https://www.simbahosting.co.uk/s3/shop/)** | **[Other useful plugins](https://profiles.wordpress.org/davidanderson#content-plugins)**
29
 
30
  == Installation ==
@@ -37,10 +35,16 @@ This plugin is offered and maintained as a free service to the WP community. You
37
 
38
  a) `define('FS_METHOD', 'ssh2');`
39
 
40
- b) Others as <a href="https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants">detailed in the official WP codex</a>
41
 
42
  == Changelog ==
43
 
 
 
 
 
 
 
44
  = 0.8.3 - 2020/Dec/19 =
45
 
46
  * TWEAK: Replace deprecated jQuery style
@@ -120,4 +124,4 @@ b) Others as <a href="https://codex.wordpress.org/Editing_wp-config.php#WordPres
120
  * Initial Release
121
 
122
  == Upgrade Notice ==
123
- * 0.8.3 : Replace deprecated jQuery style
3
  Donate link: https://sourceforge.net/donate/index.php?group_id=198487
4
  Tags: ssh, sftp
5
  Requires at least: 3.1
6
+ Tested up to: 6.1
7
+ Stable tag: 0.8.4
8
 
9
+ "SSH SFTP Updater Support" is the easiest way to keep your WordPress installation up-to-date with SFTP.
10
 
11
  == Description ==
12
 
23
  * **[UpdraftPlus](https://updraftplus.com/?ref=212&source=sshsmtp)** simplifies backups and restoration. It is the #1 most-used backup/restore plugin, with over a million currently-active installs.
24
  * **[UpdraftCentral](https://updraftplus.com/updraftcentral/?ref=212&source=sshsmtp)** - a highly efficient way to manage, optimize, update and backup multiple websites from one place.
25
  * **[WP-Optimize](https://getwpo.com/)** helps you to optimize and clean your WordPress database so that it runs at maximum efficiency.
 
 
26
  * **More quality plugins**: **[Premium WooCommerce extensions](https://www.simbahosting.co.uk/s3/shop/)** | **[Other useful plugins](https://profiles.wordpress.org/davidanderson#content-plugins)**
27
 
28
  == Installation ==
35
 
36
  a) `define('FS_METHOD', 'ssh2');`
37
 
38
+ b) Others as <a href="https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants">detailed in the official WP codex</a> or various other articles (Google for things like WordPress updates via SFTP).
39
 
40
  == Changelog ==
41
 
42
+ = 0.8.4 - 2020/Dec/30 =
43
+
44
+ * TWEAK: Remove obsolete references to other plugins
45
+ * TWEAK: Replace some further deprecated jQuery styles
46
+ * TWEAK: Update to latest 1.0.x version of phpseclib
47
+
48
  = 0.8.3 - 2020/Dec/19 =
49
 
50
  * TWEAK: Replace deprecated jQuery style
124
  * Initial Release
125
 
126
  == Upgrade Notice ==
127
+ * 0.8.4 : Remove obsolete references to other plugins
sftp.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: SSH SFTP Updater Support
4
  Plugin URI: https://wordpress.org/plugins/ssh-sftp-updater-support/
5
  Description: Update your WordPress blog / plugins via SFTP without libssh2
6
- Version: 0.8.3
7
  Author: TerraFrost, David Anderson + Team Updraft
8
  Author URI: https://updraftplus.com/
9
  */
@@ -12,7 +12,7 @@ if (!defined('ABSPATH')) die('No direct access allowed');
12
 
13
  define('SSH_SFTP_UPDATER_SUPPORT_MAIN_PATH', plugin_dir_path(__FILE__));
14
  define('SSH_SFTP_UPDATER_SUPPORT_BASENAME', plugin_basename(__FILE__));
15
- define('SSH_SFTP_UPDATER_SUPPORT_VERSION', '0.8.3');
16
  define('SSH_SFTP_UPDATER_SUPPORT_URL', plugin_dir_url(__FILE__));
17
  // see http://adambrown.info/p/wp_hooks/hook/<filter name>
18
  add_filter('filesystem_method', 'phpseclib_filesystem_method', 10, 2); // since 2.6 - WordPress will ignore the ssh option if the php ssh extension is not loaded
@@ -150,7 +150,7 @@ jQuery(function($){
150
  jQuery("#ftp, #ftps").on('click', function () {
151
  jQuery(".ssh_keys").hide();
152
  });
153
- jQuery("#private_key_file").change(function (event) {
154
  if (window.File && window.FileReader) {
155
  var reader = new FileReader();
156
  reader.onload = function(file) {
@@ -163,12 +163,12 @@ jQuery(function($){
163
  if(typeof(Storage)!=="undefined") {
164
  localStorage.privateKeyFile = jQuery("#private_key").val();
165
  }
166
- jQuery("#private_key_file").attr("disabled", "disabled");
167
  });
168
  if(typeof(Storage)!=="undefined" && localStorage.privateKeyFile) {
169
  jQuery("#private_key").val(localStorage.privateKeyFile);
170
  }
171
- jQuery('form input[value=""]:first').focus();
172
  });
173
  -->
174
  </script>
3
  Plugin Name: SSH SFTP Updater Support
4
  Plugin URI: https://wordpress.org/plugins/ssh-sftp-updater-support/
5
  Description: Update your WordPress blog / plugins via SFTP without libssh2
6
+ Version: 0.8.4
7
  Author: TerraFrost, David Anderson + Team Updraft
8
  Author URI: https://updraftplus.com/
9
  */
12
 
13
  define('SSH_SFTP_UPDATER_SUPPORT_MAIN_PATH', plugin_dir_path(__FILE__));
14
  define('SSH_SFTP_UPDATER_SUPPORT_BASENAME', plugin_basename(__FILE__));
15
+ define('SSH_SFTP_UPDATER_SUPPORT_VERSION', '0.8.4');
16
  define('SSH_SFTP_UPDATER_SUPPORT_URL', plugin_dir_url(__FILE__));
17
  // see http://adambrown.info/p/wp_hooks/hook/<filter name>
18
  add_filter('filesystem_method', 'phpseclib_filesystem_method', 10, 2); // since 2.6 - WordPress will ignore the ssh option if the php ssh extension is not loaded
150
  jQuery("#ftp, #ftps").on('click', function () {
151
  jQuery(".ssh_keys").hide();
152
  });
153
+ jQuery("#private_key_file").on('change', function (event) {
154
  if (window.File && window.FileReader) {
155
  var reader = new FileReader();
156
  reader.onload = function(file) {
163
  if(typeof(Storage)!=="undefined") {
164
  localStorage.privateKeyFile = jQuery("#private_key").val();
165
  }
166
+ jQuery("#private_key_file").prop('disabled', true);
167
  });
168
  if(typeof(Storage)!=="undefined" && localStorage.privateKeyFile) {
169
  jQuery("#private_key").val(localStorage.privateKeyFile);
170
  }
171
+ jQuery('form input[value=""]:first').trigger('focus');
172
  });
173
  -->
174
  </script>
templates/notices/horizontal-notice.php CHANGED
@@ -41,12 +41,8 @@
41
  $button_text = __('Find out more.', 'ssh-sftp-updater-support');
42
  } elseif ('ssh-sftp-updater-support' == $button_meta) {
43
  $button_text = __('Find out more.', 'ssh-sftp-updater-support');
44
- } elseif ('keyy' == $button_meta) {
45
- $button_text = __('Get Keyy', 'ssh-sftp-updater-support');
46
  } elseif ('wp-optimize' == $button_meta) {
47
  $button_text = __('Get WP-Optimize', 'ssh-sftp-updater-support');
48
- } elseif ('metaslider' == $button_meta) {
49
- $button_text = __('Get MetaSlider', 'ssh-sftp-updater-support');
50
  }
51
  $ssh_sftp_updater_support->ssh_sftp_updater_support_url($button_link, $button_text, null, 'class="updraft_notice_link"');
52
  }
41
  $button_text = __('Find out more.', 'ssh-sftp-updater-support');
42
  } elseif ('ssh-sftp-updater-support' == $button_meta) {
43
  $button_text = __('Find out more.', 'ssh-sftp-updater-support');
 
 
44
  } elseif ('wp-optimize' == $button_meta) {
45
  $button_text = __('Get WP-Optimize', 'ssh-sftp-updater-support');
 
 
46
  }
47
  $ssh_sftp_updater_support->ssh_sftp_updater_support_url($button_link, $button_text, null, 'class="updraft_notice_link"');
48
  }
templates/notices/thanks-for-using-main-dash.php CHANGED
@@ -21,14 +21,6 @@
21
  <?php printf(__('%s helps you to optimize and clean your WordPress database so that it runs at maximum efficiency.', 'ssh-sftp-updater-support'), '<strong>'.$ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://getwpo.com/', 'WP-Optimize').': </strong>'); ?>
22
  </p>
23
 
24
- <p>
25
- <?php printf(__('%s Simple and secure logins with a wave of your phone.', 'ssh-sftp-updater-support'), '<strong>'.$ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://getkeyy.com/', 'Keyy').': </strong>'); ?>
26
- </p>
27
-
28
- <p>
29
- <?php printf(__('%s Easily create beautifully-designed sliders with the #1-most installed WP slider plugin.', 'ssh-sftp-updater-support'), '<strong>'.$ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://www.metaslider.com', 'MetaSlider').': </strong>'); ?>
30
- </p>
31
-
32
  <p>
33
  <?php '<strong>'.__('More quality plugins', 'ssh-sftp-updater-support').': </strong>';?>
34
  <strong><?php $ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://www.simbahosting.co.uk/s3/shop/', __('Premium WooCommerce extensions', 'ssh-sftp-updater-support')); ?></strong> | <strong><?php $ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://profiles.wordpress.org/davidanderson#content-plugins', __('Other useful plugins', 'ssh-sftp-updater-support')); ?></a></strong>
21
  <?php printf(__('%s helps you to optimize and clean your WordPress database so that it runs at maximum efficiency.', 'ssh-sftp-updater-support'), '<strong>'.$ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://getwpo.com/', 'WP-Optimize').': </strong>'); ?>
22
  </p>
23
 
 
 
 
 
 
 
 
 
24
  <p>
25
  <?php '<strong>'.__('More quality plugins', 'ssh-sftp-updater-support').': </strong>';?>
26
  <strong><?php $ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://www.simbahosting.co.uk/s3/shop/', __('Premium WooCommerce extensions', 'ssh-sftp-updater-support')); ?></strong> | <strong><?php $ssh_sftp_updater_support->ssh_sftp_updater_support_url('https://profiles.wordpress.org/davidanderson#content-plugins', __('Other useful plugins', 'ssh-sftp-updater-support')); ?></a></strong>