SFTP.php 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4. * Pure-PHP implementation of SFTP.
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * Currently only supports SFTPv3, 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 MMIX Jim Wigginton
  52. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  53. * @link http://phpseclib.sourceforge.net
  54. */
  55. /**
  56. * Include Net_SSH2
  57. */
  58. require_once('Net/SSH2.php');
  59. /**#@+
  60. * @access public
  61. * @see Net_SFTP::getLog()
  62. */
  63. /**
  64. * Returns the message numbers
  65. */
  66. define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
  67. /**
  68. * Returns the message content
  69. */
  70. define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
  71. /**#@-*/
  72. /**
  73. * SFTP channel constant
  74. *
  75. * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
  76. *
  77. * @see Net_SSH2::_send_channel_packet()
  78. * @see Net_SSH2::_get_channel_packet()
  79. * @access private
  80. */
  81. define('NET_SFTP_CHANNEL', 2);
  82. /**#@+
  83. * @access public
  84. * @see Net_SFTP::put()
  85. */
  86. /**
  87. * Reads data from a local file.
  88. */
  89. define('NET_SFTP_LOCAL_FILE', 1);
  90. /**
  91. * Reads data from a string.
  92. */
  93. define('NET_SFTP_STRING', 2);
  94. /**#@-*/
  95. /**
  96. * Pure-PHP implementations of SFTP.
  97. *
  98. * @author Jim Wigginton <terrafrost@php.net>
  99. * @version 0.1.0
  100. * @access public
  101. * @package Net_SFTP
  102. */
  103. class Net_SFTP extends Net_SSH2 {
  104. /**
  105. * Packet Types
  106. *
  107. * @see Net_SFTP::Net_SFTP()
  108. * @var Array
  109. * @access private
  110. */
  111. var $packet_types = array();
  112. /**
  113. * Status Codes
  114. *
  115. * @see Net_SFTP::Net_SFTP()
  116. * @var Array
  117. * @access private
  118. */
  119. var $status_codes = array();
  120. /**
  121. * The Request ID
  122. *
  123. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  124. * concurrent actions, so it's somewhat academic, here.
  125. *
  126. * @var Integer
  127. * @see Net_SFTP::_send_sftp_packet()
  128. * @access private
  129. */
  130. var $request_id = false;
  131. /**
  132. * The Packet Type
  133. *
  134. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  135. * concurrent actions, so it's somewhat academic, here.
  136. *
  137. * @var Integer
  138. * @see Net_SFTP::_get_sftp_packet()
  139. * @access private
  140. */
  141. var $packet_type = -1;
  142. /**
  143. * Packet Buffer
  144. *
  145. * @var String
  146. * @see Net_SFTP::_get_sftp_packet()
  147. * @access private
  148. */
  149. var $packet_buffer = '';
  150. /**
  151. * Extensions supported by the server
  152. *
  153. * @var Array
  154. * @see Net_SFTP::_initChannel()
  155. * @access private
  156. */
  157. var $extensions = array();
  158. /**
  159. * Server SFTP version
  160. *
  161. * @var Integer
  162. * @see Net_SFTP::_initChannel()
  163. * @access private
  164. */
  165. var $version;
  166. /**
  167. * Current working directory
  168. *
  169. * @var String
  170. * @see Net_SFTP::_realpath()
  171. * @see Net_SFTP::chdir()
  172. * @access private
  173. */
  174. var $pwd = false;
  175. /**
  176. * Packet Type Log
  177. *
  178. * @see Net_SFTP::getLog()
  179. * @var Array
  180. * @access private
  181. */
  182. var $packet_type_log = array();
  183. /**
  184. * Packet Log
  185. *
  186. * @see Net_SFTP::getLog()
  187. * @var Array
  188. * @access private
  189. */
  190. var $packet_log = array();
  191. /**
  192. * Error information
  193. *
  194. * @see Net_SFTP::getSFTPErrors()
  195. * @see Net_SFTP::getLastSFTPError()
  196. * @var String
  197. * @access private
  198. */
  199. var $sftp_errors = array();
  200. /**
  201. * File Type
  202. *
  203. * @see Net_SFTP::_parseLongname()
  204. * @var Integer
  205. * @access private
  206. */
  207. var $fileType = 0;
  208. /**
  209. * Default Constructor.
  210. *
  211. * Connects to an SFTP server
  212. *
  213. * @param String $host
  214. * @param optional Integer $port
  215. * @param optional Integer $timeout
  216. * @return Net_SFTP
  217. * @access public
  218. */
  219. function Net_SFTP($host, $port = 22, $timeout = 10)
  220. {
  221. parent::Net_SSH2($host, $port, $timeout);
  222. $this->packet_types = array(
  223. 1 => 'NET_SFTP_INIT',
  224. 2 => 'NET_SFTP_VERSION',
  225. /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
  226. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
  227. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
  228. 3 => 'NET_SFTP_OPEN',
  229. 4 => 'NET_SFTP_CLOSE',
  230. 5 => 'NET_SFTP_READ',
  231. 6 => 'NET_SFTP_WRITE',
  232. 7 => 'NET_SFTP_LSTAT',
  233. 9 => 'NET_SFTP_SETSTAT',
  234. 11 => 'NET_SFTP_OPENDIR',
  235. 12 => 'NET_SFTP_READDIR',
  236. 13 => 'NET_SFTP_REMOVE',
  237. 14 => 'NET_SFTP_MKDIR',
  238. 15 => 'NET_SFTP_RMDIR',
  239. 16 => 'NET_SFTP_REALPATH',
  240. 17 => 'NET_SFTP_STAT',
  241. /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
  242. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  243. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
  244. 18 => 'NET_SFTP_RENAME',
  245. 101=> 'NET_SFTP_STATUS',
  246. 102=> 'NET_SFTP_HANDLE',
  247. /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
  248. SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
  249. pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
  250. 103=> 'NET_SFTP_DATA',
  251. 104=> 'NET_SFTP_NAME',
  252. 105=> 'NET_SFTP_ATTRS',
  253. 200=> 'NET_SFTP_EXTENDED'
  254. );
  255. $this->status_codes = array(
  256. 0 => 'NET_SFTP_STATUS_OK',
  257. 1 => 'NET_SFTP_STATUS_EOF',
  258. 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
  259. 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
  260. 4 => 'NET_SFTP_STATUS_FAILURE',
  261. 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
  262. 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
  263. 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
  264. 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED'
  265. );
  266. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
  267. // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
  268. $this->attributes = array(
  269. 0x00000001 => 'NET_SFTP_ATTR_SIZE',
  270. 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
  271. 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
  272. 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
  273. // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
  274. // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
  275. // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
  276. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
  277. -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
  278. );
  279. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
  280. // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
  281. // the array for that $this->open5_flags and similarily alter the constant names.
  282. $this->open_flags = array(
  283. 0x00000001 => 'NET_SFTP_OPEN_READ',
  284. 0x00000002 => 'NET_SFTP_OPEN_WRITE',
  285. 0x00000008 => 'NET_SFTP_OPEN_CREATE',
  286. 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE'
  287. );
  288. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
  289. // see Net_SFTP::_parseLongname() for an explanation
  290. $this->file_types = array(
  291. 1 => 'NET_SFTP_TYPE_REGULAR',
  292. 2 => 'NET_SFTP_TYPE_DIRECTORY',
  293. 3 => 'NET_SFTP_TYPE_SYMLINK',
  294. 4 => 'NET_SFTP_TYPE_SPECIAL'
  295. );
  296. $this->_define_array(
  297. $this->packet_types,
  298. $this->status_codes,
  299. $this->attributes,
  300. $this->open_flags,
  301. $this->file_types
  302. );
  303. }
  304. /**
  305. * Login
  306. *
  307. * @param String $username
  308. * @param optional String $password
  309. * @return Boolean
  310. * @access public
  311. */
  312. function login($username, $password = '')
  313. {
  314. if (!parent::login($username, $password)) {
  315. return false;
  316. }
  317. $this->window_size_client_to_server[NET_SFTP_CHANNEL] = $this->window_size;
  318. $packet = pack('CNa*N3',
  319. NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
  320. if (!$this->_send_binary_packet($packet)) {
  321. return false;
  322. }
  323. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
  324. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  325. if ($response === false) {
  326. return false;
  327. }
  328. $packet = pack('CNNa*CNa*',
  329. NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
  330. if (!$this->_send_binary_packet($packet)) {
  331. return false;
  332. }
  333. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  334. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  335. if ($response === false) {
  336. return false;
  337. }
  338. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
  339. if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
  340. return false;
  341. }
  342. $response = $this->_get_sftp_packet();
  343. if ($this->packet_type != NET_SFTP_VERSION) {
  344. user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE);
  345. return false;
  346. }
  347. extract(unpack('Nversion', $this->_string_shift($response, 4)));
  348. $this->version = $version;
  349. while (!empty($response)) {
  350. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  351. $key = $this->_string_shift($response, $length);
  352. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  353. $value = $this->_string_shift($response, $length);
  354. $this->extensions[$key] = $value;
  355. }
  356. /*
  357. SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
  358. however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
  359. not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
  360. one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
  361. 'newline@vandyke.com' would.
  362. */
  363. /*
  364. if (isset($this->extensions['newline@vandyke.com'])) {
  365. $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
  366. unset($this->extensions['newline@vandyke.com']);
  367. }
  368. */
  369. $this->request_id = 1;
  370. /*
  371. A Note on SFTPv4/5/6 support:
  372. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
  373. "If the client wishes to interoperate with servers that support noncontiguous version
  374. numbers it SHOULD send '3'"
  375. Given that the server only sends its version number after the client has already done so, the above
  376. seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
  377. most popular.
  378. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
  379. "If the server did not send the "versions" extension, or the version-from-list was not included, the
  380. server MAY send a status response describing the failure, but MUST then close the channel without
  381. processing any further requests."
  382. So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
  383. a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
  384. v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
  385. in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
  386. channel and reopen it with a new and updated SSH_FXP_INIT packet.
  387. */
  388. if ($this->version != 3) {
  389. return false;
  390. }
  391. $this->pwd = $this->_realpath('.');
  392. return true;
  393. }
  394. /**
  395. * Returns the current directory name
  396. *
  397. * @return Mixed
  398. * @access public
  399. */
  400. function pwd()
  401. {
  402. return $this->pwd;
  403. }
  404. /**
  405. * Canonicalize the Server-Side Path Name
  406. *
  407. * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
  408. * the absolute (canonicalized) path. If $mode is set to NET_SFTP_CONFIRM_DIR (as opposed to NET_SFTP_CONFIRM_NONE,
  409. * which is what it is set to by default), false is returned if $dir is not a valid directory.
  410. *
  411. * @see Net_SFTP::chdir()
  412. * @param String $dir
  413. * @param optional Integer $mode
  414. * @return Mixed
  415. * @access private
  416. */
  417. function _realpath($dir)
  418. {
  419. /*
  420. "This protocol represents file names as strings. File names are
  421. assumed to use the slash ('/') character as a directory separator.
  422. File names starting with a slash are "absolute", and are relative to
  423. the root of the file system. Names starting with any other character
  424. are relative to the user's default directory (home directory). Note
  425. that identifying the user is assumed to take place outside of this
  426. protocol."
  427. -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6
  428. */
  429. $file = '';
  430. if ($this->pwd !== false) {
  431. // if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary
  432. // on OpenSSH it isn't necessary but on other SFTP servers it is. that and since the specs say nothing on
  433. // the subject, we'll go ahead and work around it with the following.
  434. if ($dir[strlen($dir) - 1] != '/') {
  435. $file = basename($dir);
  436. $dir = dirname($dir);
  437. }
  438. if ($dir == '.' || $dir == $this->pwd) {
  439. return $this->pwd . $file;
  440. }
  441. if ($dir[0] != '/') {
  442. $dir = $this->pwd . '/' . $dir;
  443. }
  444. // on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths
  445. // can contain .'s and ..'s just like any other. we could parse those out as appropriate or we can let
  446. // the server do it. we'll do the latter.
  447. }
  448. /*
  449. that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the
  450. specified path. generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet
  451. regardless of whether or not a file actually exists. and in SFTPv3, the longname field and the filename
  452. field match for this particular SSH_FXP_NAME packet. for other SSH_FXP_NAME packets, this will likely
  453. not be the case, but for this one, it is.
  454. */
  455. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
  456. if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($dir), $dir))) {
  457. return false;
  458. }
  459. $response = $this->_get_sftp_packet();
  460. switch ($this->packet_type) {
  461. case NET_SFTP_NAME:
  462. // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
  463. // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
  464. // at is the first part and that part is defined the same in SFTP versions 3 through 6.
  465. $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
  466. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  467. $realpath = $this->_string_shift($response, $length);
  468. // the following is SFTPv3 only code. see Net_SFTP::_parseLongname() for more information.
  469. // per the above comment, this is a shot in the dark that, on most servers, won't help us in determining
  470. // the file type for Net_SFTP::stat() and Net_SFTP::lstat() but it's worth a shot.
  471. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  472. $this->fileType = $this->_parseLongname($this->_string_shift($response, $length));
  473. break;
  474. case NET_SFTP_STATUS:
  475. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  476. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  477. return false;
  478. default:
  479. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
  480. return false;
  481. }
  482. // if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to
  483. // be a bonafide directory
  484. return $realpath . '/' . $file;
  485. }
  486. /**
  487. * Changes the current directory
  488. *
  489. * @param String $dir
  490. * @return Boolean
  491. * @access public
  492. */
  493. function chdir($dir)
  494. {
  495. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  496. return false;
  497. }
  498. if ($dir[strlen($dir) - 1] != '/') {
  499. $dir.= '/';
  500. }
  501. $dir = $this->_realpath($dir);
  502. // confirm that $dir is, in fact, a valid directory
  503. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  504. return false;
  505. }
  506. // see Net_SFTP::nlist() for a more thorough explanation of the following
  507. $response = $this->_get_sftp_packet();
  508. switch ($this->packet_type) {
  509. case NET_SFTP_HANDLE:
  510. $handle = substr($response, 4);
  511. break;
  512. case NET_SFTP_STATUS:
  513. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  514. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  515. return false;
  516. default:
  517. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
  518. return false;
  519. }
  520. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  521. return false;
  522. }
  523. $response = $this->_get_sftp_packet();
  524. if ($this->packet_type != NET_SFTP_STATUS) {
  525. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  526. return false;
  527. }
  528. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  529. if ($status != NET_SFTP_STATUS_OK) {
  530. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  531. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  532. return false;
  533. }
  534. $this->pwd = $dir;
  535. return true;
  536. }
  537. /**
  538. * Returns a list of files in the given directory
  539. *
  540. * @param optional String $dir
  541. * @return Mixed
  542. * @access public
  543. */
  544. function nlist($dir = '.')
  545. {
  546. return $this->_list($dir, false);
  547. }
  548. /**
  549. * Returns a detailed list of files in the given directory
  550. *
  551. * @param optional String $dir
  552. * @return Mixed
  553. * @access public
  554. */
  555. function rawlist($dir = '.')
  556. {
  557. return $this->_list($dir, true);
  558. }
  559. /**
  560. * Reads a list, be it detailed or not, of files in the given directory
  561. *
  562. * @param optional String $dir
  563. * @return Mixed
  564. * @access private
  565. */
  566. function _list($dir, $raw = true)
  567. {
  568. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  569. return false;
  570. }
  571. $dir = $this->_realpath($dir);
  572. if ($dir === false) {
  573. return false;
  574. }
  575. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
  576. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  577. return false;
  578. }
  579. $response = $this->_get_sftp_packet();
  580. switch ($this->packet_type) {
  581. case NET_SFTP_HANDLE:
  582. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
  583. // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
  584. // represent the length of the string and leave it at that
  585. $handle = substr($response, 4);
  586. break;
  587. case NET_SFTP_STATUS:
  588. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  589. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  590. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  591. return false;
  592. default:
  593. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
  594. return false;
  595. }
  596. $contents = array();
  597. while (true) {
  598. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
  599. // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
  600. // SSH_MSG_CHANNEL_DATA messages is not known to me.
  601. if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
  602. return false;
  603. }
  604. $response = $this->_get_sftp_packet();
  605. switch ($this->packet_type) {
  606. case NET_SFTP_NAME:
  607. extract(unpack('Ncount', $this->_string_shift($response, 4)));
  608. for ($i = 0; $i < $count; $i++) {
  609. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  610. $shortname = $this->_string_shift($response, $length);
  611. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  612. $longname = $this->_string_shift($response, $length);
  613. $attributes = $this->_parseAttributes($response); // we also don't care about the attributes
  614. if (!$raw) {
  615. $contents[] = $shortname;
  616. } else {
  617. $contents[$shortname] = $attributes;
  618. $fileType = $this->_parseLongname($longname);
  619. if ($fileType) {
  620. $contents[$shortname]['type'] = $fileType;
  621. }
  622. }
  623. // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
  624. // final SSH_FXP_STATUS packet should tell us that, already.
  625. }
  626. break;
  627. case NET_SFTP_STATUS:
  628. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  629. if ($status != NET_SFTP_STATUS_EOF) {
  630. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  631. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  632. return false;
  633. }
  634. break 2;
  635. default:
  636. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE);
  637. return false;
  638. }
  639. }
  640. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  641. return false;
  642. }
  643. // "The client MUST release all resources associated with the handle regardless of the status."
  644. // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
  645. $response = $this->_get_sftp_packet();
  646. if ($this->packet_type != NET_SFTP_STATUS) {
  647. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  648. return false;
  649. }
  650. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  651. if ($status != NET_SFTP_STATUS_OK) {
  652. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  653. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  654. return false;
  655. }
  656. return $contents;
  657. }
  658. /**
  659. * Returns the file size, in bytes, or false, on failure
  660. *
  661. * Files larger than 4GB will show up as being exactly 4GB.
  662. *
  663. * @param String $filename
  664. * @return Mixed
  665. * @access public
  666. */
  667. function size($filename)
  668. {
  669. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  670. return false;
  671. }
  672. $filename = $this->_realpath($filename);
  673. if ($filename === false) {
  674. return false;
  675. }
  676. return $this->_size($filename);
  677. }
  678. /**
  679. * Returns general information about a file.
  680. *
  681. * Returns an array on success and false otherwise.
  682. *
  683. * @param String $filename
  684. * @return Mixed
  685. * @access public
  686. */
  687. function stat($filename)
  688. {
  689. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  690. return false;
  691. }
  692. $filename = $this->_realpath($filename);
  693. if ($filename === false) {
  694. return false;
  695. }
  696. return $this->_stat($filename, NET_SFTP_STAT);
  697. }
  698. /**
  699. * Returns general information about a file or symbolic link.
  700. *
  701. * Returns an array on success and false otherwise.
  702. *
  703. * @param String $filename
  704. * @return Mixed
  705. * @access public
  706. */
  707. function lstat($filename)
  708. {
  709. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  710. return false;
  711. }
  712. $filename = $this->_realpath($filename);
  713. if ($filename === false) {
  714. return false;
  715. }
  716. return $this->_stat($filename, NET_SFTP_LSTAT);
  717. }
  718. /**
  719. * Returns general information about a file or symbolic link
  720. *
  721. * Determines information without calling Net_SFTP::_realpath().
  722. * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
  723. *
  724. * @param String $filename
  725. * @param Integer $type
  726. * @return Mixed
  727. * @access private
  728. */
  729. function _stat($filename, $type)
  730. {
  731. // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  732. $packet = pack('Na*', strlen($filename), $filename);
  733. if (!$this->_send_sftp_packet($type, $packet)) {
  734. return false;
  735. }
  736. $response = $this->_get_sftp_packet();
  737. switch ($this->packet_type) {
  738. case NET_SFTP_ATTRS:
  739. $attributes = $this->_parseAttributes($response);
  740. if ($this->fileType) {
  741. $attributes['type'] = $this->fileType;
  742. }
  743. return $attributes;
  744. case NET_SFTP_STATUS:
  745. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  746. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  747. return false;
  748. }
  749. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
  750. return false;
  751. }
  752. /**
  753. * Returns the file size, in bytes, or false, on failure
  754. *
  755. * Determines the size without calling Net_SFTP::_realpath()
  756. *
  757. * @param String $filename
  758. * @return Mixed
  759. * @access private
  760. */
  761. function _size($filename)
  762. {
  763. $result = $this->_stat($filename, NET_SFTP_LSTAT);
  764. return $result === false ? false : $result['size'];
  765. }
  766. /**
  767. * Set permissions on a file.
  768. *
  769. * Returns the new file permissions on success or FALSE on error.
  770. *
  771. * @param Integer $mode
  772. * @param String $filename
  773. * @return Mixed
  774. * @access public
  775. */
  776. function chmod($mode, $filename)
  777. {
  778. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  779. return false;
  780. }
  781. $filename = $this->_realpath($filename);
  782. if ($filename === false) {
  783. return false;
  784. }
  785. // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
  786. // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
  787. $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
  788. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
  789. return false;
  790. }
  791. /*
  792. "Because some systems must use separate system calls to set various attributes, it is possible that a failure
  793. response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
  794. servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
  795. -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
  796. */
  797. $response = $this->_get_sftp_packet();
  798. if ($this->packet_type != NET_SFTP_STATUS) {
  799. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  800. return false;
  801. }
  802. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  803. if ($status != NET_SFTP_STATUS_EOF) {
  804. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  805. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  806. }
  807. // rather than return what the permissions *should* be, we'll return what they actually are. this will also
  808. // tell us if the file actually exists.
  809. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  810. $packet = pack('Na*', strlen($filename), $filename);
  811. if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
  812. return false;
  813. }
  814. $response = $this->_get_sftp_packet();
  815. switch ($this->packet_type) {
  816. case NET_SFTP_ATTRS:
  817. $attrs = $this->_parseAttributes($response);
  818. return $attrs['permissions'];
  819. case NET_SFTP_STATUS:
  820. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  821. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  822. return false;
  823. }
  824. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE);
  825. return false;
  826. }
  827. /**
  828. * Creates a directory.
  829. *
  830. * @param String $dir
  831. * @return Boolean
  832. * @access public
  833. */
  834. function mkdir($dir)
  835. {
  836. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  837. return false;
  838. }
  839. $dir = $this->_realpath(rtrim($dir, '/'));
  840. if ($dir === false) {
  841. return false;
  842. }
  843. // by not providing any permissions, hopefully the server will use the logged in users umask - their
  844. // default permissions.
  845. if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*N', strlen($dir), $dir, 0))) {
  846. return false;
  847. }
  848. $response = $this->_get_sftp_packet();
  849. if ($this->packet_type != NET_SFTP_STATUS) {
  850. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  851. return false;
  852. }
  853. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  854. if ($status != NET_SFTP_STATUS_OK) {
  855. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  856. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  857. return false;
  858. }
  859. return true;
  860. }
  861. /**
  862. * Removes a directory.
  863. *
  864. * @param String $dir
  865. * @return Boolean
  866. * @access public
  867. */
  868. function rmdir($dir)
  869. {
  870. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  871. return false;
  872. }
  873. $dir = $this->_realpath($dir);
  874. if ($dir === false) {
  875. return false;
  876. }
  877. if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
  878. return false;
  879. }
  880. $response = $this->_get_sftp_packet();
  881. if ($this->packet_type != NET_SFTP_STATUS) {
  882. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  883. return false;
  884. }
  885. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  886. if ($status != NET_SFTP_STATUS_OK) {
  887. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
  888. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  889. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  890. return false;
  891. }
  892. return true;
  893. }
  894. /**
  895. * Uploads a file to the SFTP server.
  896. *
  897. * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
  898. * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
  899. * long, containing 'filename.ext' as its contents.
  900. *
  901. * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
  902. * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
  903. * large $remote_file will be, as well.
  904. *
  905. * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
  906. * care of that, yourself.
  907. *
  908. * @param String $remote_file
  909. * @param String $data
  910. * @param optional Integer $mode
  911. * @return Boolean
  912. * @access public
  913. * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
  914. */
  915. function put($remote_file, $data, $mode = NET_SFTP_STRING)
  916. {
  917. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  918. return false;
  919. }
  920. $remote_file = $this->_realpath($remote_file);
  921. if ($remote_file === false) {
  922. return false;
  923. }
  924. $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_TRUNCATE, 0);
  925. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  926. return false;
  927. }
  928. $response = $this->_get_sftp_packet();
  929. switch ($this->packet_type) {
  930. case NET_SFTP_HANDLE:
  931. $handle = substr($response, 4);
  932. break;
  933. case NET_SFTP_STATUS:
  934. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  935. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  936. return false;
  937. default:
  938. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
  939. return false;
  940. }
  941. $initialize = true;
  942. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
  943. if ($mode == NET_SFTP_LOCAL_FILE) {
  944. if (!is_file($data)) {
  945. user_error("$data is not a valid file", E_USER_NOTICE);
  946. return false;
  947. }
  948. $fp = @fopen($data, 'rb');
  949. if (!$fp) {
  950. return false;
  951. }
  952. $sent = 0;
  953. $size = filesize($data);
  954. } else {
  955. $sent = 0;
  956. $size = strlen($data);
  957. }
  958. $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
  959. $sftp_packet_size = 4096; // PuTTY uses 4096
  960. $i = 0;
  961. while ($sent < $size) {
  962. $temp = $mode == NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
  963. $packet = pack('Na*N3a*', strlen($handle), $handle, 0, $sent, strlen($temp), $temp);
  964. if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
  965. fclose($fp);
  966. return false;
  967. }
  968. $sent+= strlen($temp);
  969. $i++;
  970. if ($i == 50) {
  971. if (!$this->_read_put_responses($i)) {
  972. $i = 0;
  973. break;
  974. }
  975. $i = 0;
  976. }
  977. }
  978. $this->_read_put_responses($i);
  979. if ($mode == NET_SFTP_LOCAL_FILE) {
  980. fclose($fp);
  981. }
  982. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  983. return false;
  984. }
  985. $response = $this->_get_sftp_packet();
  986. if ($this->packet_type != NET_SFTP_STATUS) {
  987. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  988. return false;
  989. }
  990. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  991. if ($status != NET_SFTP_STATUS_OK) {
  992. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  993. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  994. return false;
  995. }
  996. return true;
  997. }
  998. /**
  999. * Reads multiple successive SSH_FXP_WRITE responses
  1000. *
  1001. * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
  1002. * SSH_FXP_WRITEs, in succession, and then reading $i responses.
  1003. *
  1004. * @param Integer $i
  1005. * @return Boolean
  1006. * @access private
  1007. */
  1008. function _read_put_responses($i)
  1009. {
  1010. while ($i--) {
  1011. $response = $this->_get_sftp_packet();
  1012. if ($this->packet_type != NET_SFTP_STATUS) {
  1013. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  1014. return false;
  1015. }
  1016. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1017. if ($status != NET_SFTP_STATUS_OK) {
  1018. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1019. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1020. break;
  1021. }
  1022. }
  1023. return $i < 0;
  1024. }
  1025. /**
  1026. * Downloads a file from the SFTP server.
  1027. *
  1028. * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
  1029. * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
  1030. * operation
  1031. *
  1032. * @param String $remote_file
  1033. * @param optional String $local_file
  1034. * @return Mixed
  1035. * @access public
  1036. */
  1037. function get($remote_file, $local_file = false)
  1038. {
  1039. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1040. return false;
  1041. }
  1042. $remote_file = $this->_realpath($remote_file);
  1043. if ($remote_file === false) {
  1044. return false;
  1045. }
  1046. $size = $this->_size($remote_file);
  1047. if ($size === false) {
  1048. return false;
  1049. }
  1050. $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
  1051. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  1052. return false;
  1053. }
  1054. $response = $this->_get_sftp_packet();
  1055. switch ($this->packet_type) {
  1056. case NET_SFTP_HANDLE:
  1057. $handle = substr($response, 4);
  1058. break;
  1059. case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1060. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  1061. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1062. return false;
  1063. default:
  1064. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE);
  1065. return false;
  1066. }
  1067. if ($local_file !== false) {
  1068. $fp = fopen($local_file, 'wb');
  1069. if (!$fp) {
  1070. return false;
  1071. }
  1072. } else {
  1073. $content = '';
  1074. }
  1075. $read = 0;
  1076. while ($read < $size) {
  1077. $packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20);
  1078. if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
  1079. return false;
  1080. }
  1081. $response = $this->_get_sftp_packet();
  1082. switch ($this->packet_type) {
  1083. case NET_SFTP_DATA:
  1084. $temp = substr($response, 4);
  1085. $read+= strlen($temp);
  1086. if ($local_file === false) {
  1087. $content.= $temp;
  1088. } else {
  1089. fputs($fp, $temp);
  1090. }
  1091. break;
  1092. case NET_SFTP_STATUS:
  1093. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  1094. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1095. break 2;
  1096. default:
  1097. user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE);
  1098. return false;
  1099. }
  1100. }
  1101. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  1102. return false;
  1103. }
  1104. $response = $this->_get_sftp_packet();
  1105. if ($this->packet_type != NET_SFTP_STATUS) {
  1106. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  1107. return false;
  1108. }
  1109. extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8)));
  1110. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1111. // check the status from the NET_SFTP_STATUS case in the above switch after the file has been closed
  1112. if ($status != NET_SFTP_STATUS_OK) {
  1113. return false;
  1114. }
  1115. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1116. if ($status != NET_SFTP_STATUS_OK) {
  1117. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1118. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1119. return false;
  1120. }
  1121. if (isset($content)) {
  1122. return $content;
  1123. }
  1124. fclose($fp);
  1125. return true;
  1126. }
  1127. /**
  1128. * Deletes a file on the SFTP server.
  1129. *
  1130. * @param String $path
  1131. * @return Boolean
  1132. * @access public
  1133. */
  1134. function delete($path)
  1135. {
  1136. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1137. return false;
  1138. }
  1139. $path = $this->_realpath($path);
  1140. if ($path === false) {
  1141. return false;
  1142. }
  1143. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  1144. if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
  1145. return false;
  1146. }
  1147. $response = $this->_get_sftp_packet();
  1148. if ($this->packet_type != NET_SFTP_STATUS) {
  1149. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  1150. return false;
  1151. }
  1152. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1153. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1154. if ($status != NET_SFTP_STATUS_OK) {
  1155. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1156. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1157. return false;
  1158. }
  1159. return true;
  1160. }
  1161. /**
  1162. * Renames a file or a directory on the SFTP server
  1163. *
  1164. * @param String $oldname
  1165. * @param String $newname
  1166. * @return Boolean
  1167. * @access public
  1168. */
  1169. function rename($oldname, $newname)
  1170. {
  1171. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1172. return false;
  1173. }
  1174. $oldname = $this->_realpath($oldname);
  1175. $newname = $this->_realpath($newname);
  1176. if ($oldname === false || $newname === false) {
  1177. return false;
  1178. }
  1179. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  1180. $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
  1181. if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
  1182. return false;
  1183. }
  1184. $response = $this->_get_sftp_packet();
  1185. if ($this->packet_type != NET_SFTP_STATUS) {
  1186. user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
  1187. return false;
  1188. }
  1189. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1190. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1191. if ($status != NET_SFTP_STATUS_OK) {
  1192. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1193. $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length);
  1194. return false;
  1195. }
  1196. return true;
  1197. }
  1198. /**
  1199. * Parse Attributes
  1200. *
  1201. * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
  1202. *
  1203. * @param String $response
  1204. * @return Array
  1205. * @access private
  1206. */
  1207. function _parseAttributes(&$response)
  1208. {
  1209. $attr = array();
  1210. extract(unpack('Nflags', $this->_string_shift($response, 4)));
  1211. // SFTPv4+ have a type field (a byte) that follows the above flag field
  1212. foreach ($this->attributes as $key => $value) {
  1213. switch ($flags & $key) {
  1214. case NET_SFTP_ATTR_SIZE: // 0x00000001
  1215. // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
  1216. // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
  1217. // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
  1218. // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
  1219. // 4GB as being 4GB.
  1220. extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
  1221. if ($upper) {
  1222. $attr['size'] = 0xFFFFFFFF;
  1223. } else {
  1224. $attr['size'] = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
  1225. }
  1226. break;
  1227. case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
  1228. $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
  1229. break;
  1230. case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
  1231. $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
  1232. break;
  1233. case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
  1234. $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
  1235. break;
  1236. case NET_SFTP_ATTR_EXTENDED: // 0x80000000
  1237. extract(unpack('Ncount', $this->_string_shift($response, 4)));
  1238. for ($i = 0; $i < $count; $i++) {
  1239. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1240. $key = $this->_string_shift($response, $length);
  1241. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1242. $attr[$key] = $this->_string_shift($response, $length);
  1243. }
  1244. }
  1245. }
  1246. return $attr;
  1247. }
  1248. /**
  1249. * Parse Longname
  1250. *
  1251. * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
  1252. * a file as a directory and see if an error is returned or you could try to parse the
  1253. * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
  1254. * The result is returned using the
  1255. * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
  1256. *
  1257. * If the longname is in an unrecognized format bool(false) is returned.
  1258. *
  1259. * @param String $longname
  1260. * @return Mixed
  1261. * @access private
  1262. */
  1263. function _parseLongname($longname)
  1264. {
  1265. // http://en.wikipedia.org/wiki/Unix_file_types
  1266. if (preg_match('#^[^/]([r-][w-][x-]){3}#', $longname)) {
  1267. switch ($longname[0]) {
  1268. case '-':
  1269. return NET_SFTP_TYPE_REGULAR;
  1270. case 'd':
  1271. return NET_SFTP_TYPE_DIRECTORY;
  1272. case 'l':
  1273. return NET_SFTP_TYPE_SYMLINK;
  1274. default:
  1275. return NET_SFTP_TYPE_SPECIAL;
  1276. }
  1277. }
  1278. return false;
  1279. }
  1280. /**
  1281. * Sends SFTP Packets
  1282. *
  1283. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  1284. *
  1285. * @param Integer $type
  1286. * @param String $data
  1287. * @see Net_SFTP::_get_sftp_packet()
  1288. * @see Net_SSH2::_send_channel_packet()
  1289. * @return Boolean
  1290. * @access private
  1291. */
  1292. function _send_sftp_packet($type, $data)
  1293. {
  1294. $packet = $this->request_id !== false ?
  1295. pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
  1296. pack('NCa*', strlen($data) + 1, $type, $data);
  1297. $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  1298. $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
  1299. $stop = strtok(microtime(), ' ') + strtok('');
  1300. if (defined('NET_SFTP_LOGGING')) {
  1301. $this->packet_type_log[] = '-> ' . $this->packet_types[$type] .
  1302. ' (' . round($stop - $start, 4) . 's)';
  1303. if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  1304. $this->packet_log[] = $data;
  1305. }
  1306. }
  1307. return $result;
  1308. }
  1309. /**
  1310. * Receives SFTP Packets
  1311. *
  1312. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  1313. *
  1314. * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
  1315. * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
  1316. * messages containing one SFTP packet.
  1317. *
  1318. * @see Net_SFTP::_send_sftp_packet()
  1319. * @return String
  1320. * @access private
  1321. */
  1322. function _get_sftp_packet()
  1323. {
  1324. $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  1325. // SFTP packet length
  1326. while (strlen($this->packet_buffer) < 4) {
  1327. $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  1328. if (is_bool($temp)) {
  1329. $this->packet_type = false;
  1330. $this->packet_buffer = '';
  1331. return false;
  1332. }
  1333. $this->packet_buffer.= $temp;
  1334. }
  1335. extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
  1336. $tempLength = $length;
  1337. $tempLength-= strlen($this->packet_buffer);
  1338. // SFTP packet type and data payload
  1339. while ($tempLength > 0) {
  1340. $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  1341. if (is_bool($temp)) {
  1342. $this->packet_type = false;
  1343. $this->packet_buffer = '';
  1344. return false;
  1345. }
  1346. $this->packet_buffer.= $temp;
  1347. $tempLength-= strlen($temp);
  1348. }
  1349. $stop = strtok(microtime(), ' ') + strtok('');
  1350. $this->packet_type = ord($this->_string_shift($this->packet_buffer));
  1351. if ($this->request_id !== false) {
  1352. $this->_string_shift($this->packet_buffer, 4); // remove the request id
  1353. $length-= 5; // account for the request id and the packet type
  1354. } else {
  1355. $length-= 1; // account for the packet type
  1356. }
  1357. $packet = $this->_string_shift($this->packet_buffer, $length);
  1358. if (defined('NET_SFTP_LOGGING')) {
  1359. $this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] .
  1360. ' (' . round($stop - $start, 4) . 's)';
  1361. if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  1362. $this->packet_log[] = $packet;
  1363. }
  1364. }
  1365. return $packet;
  1366. }
  1367. /**
  1368. * Returns a log of the packets that have been sent and received.
  1369. *
  1370. * 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')
  1371. *
  1372. * @access public
  1373. * @return String or Array
  1374. */
  1375. function getSFTPLog()
  1376. {
  1377. if (!defined('NET_SFTP_LOGGING')) {
  1378. return false;
  1379. }
  1380. switch (NET_SFTP_LOGGING) {
  1381. case NET_SFTP_LOG_COMPLEX:
  1382. return $this->_format_log($this->packet_log, $this->packet_type_log);
  1383. break;
  1384. //case NET_SFTP_LOG_SIMPLE:
  1385. default:
  1386. return $this->packet_type_log;
  1387. }
  1388. }
  1389. /**
  1390. * Returns all errors
  1391. *
  1392. * @return String
  1393. * @access public
  1394. */
  1395. function getSFTPErrors()
  1396. {
  1397. return $this->sftp_errors;
  1398. }
  1399. /**
  1400. * Returns the last error
  1401. *
  1402. * @return String
  1403. * @access public
  1404. */
  1405. function getLastSFTPError()
  1406. {
  1407. return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
  1408. }
  1409. /**
  1410. * Get supported SFTP versions
  1411. *
  1412. * @return Array
  1413. * @access public
  1414. */
  1415. function getSupportedVersions()
  1416. {
  1417. $temp = array('version' => $this->version);
  1418. if (isset($this->extensions['versions'])) {
  1419. $temp['extensions'] = $this->extensions['versions'];
  1420. }
  1421. return $temp;
  1422. }
  1423. /**
  1424. * Disconnect
  1425. *
  1426. * @param Integer $reason
  1427. * @return Boolean
  1428. * @access private
  1429. */
  1430. function _disconnect($reason)
  1431. {
  1432. $this->pwd = false;
  1433. parent::_disconnect($reason);
  1434. }
  1435. }