SFTP.php 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223
  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 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 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. if (!class_exists('Net_SSH2')) {
  59. require_once('SSH2.php');
  60. }
  61. /**#@+
  62. * @access public
  63. * @see Net_SFTP::getLog()
  64. */
  65. /**
  66. * Returns the message numbers
  67. */
  68. define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE);
  69. /**
  70. * Returns the message content
  71. */
  72. define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX);
  73. /**
  74. * Outputs the message content in real-time.
  75. */
  76. define('NET_SFTP_LOG_REALTIME', 3);
  77. /**#@-*/
  78. /**
  79. * SFTP channel constant
  80. *
  81. * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
  82. *
  83. * @see Net_SSH2::_send_channel_packet()
  84. * @see Net_SSH2::_get_channel_packet()
  85. * @access private
  86. */
  87. define('NET_SFTP_CHANNEL', 2);
  88. /**#@+
  89. * @access public
  90. * @see Net_SFTP::put()
  91. */
  92. /**
  93. * Reads data from a local file.
  94. */
  95. define('NET_SFTP_LOCAL_FILE', 1);
  96. /**
  97. * Reads data from a string.
  98. */
  99. // this value isn't really used anymore but i'm keeping it reserved for historical reasons
  100. define('NET_SFTP_STRING', 2);
  101. /**
  102. * Resumes an upload
  103. */
  104. define('NET_SFTP_RESUME', 4);
  105. /**#@-*/
  106. /**
  107. * Pure-PHP implementations of SFTP.
  108. *
  109. * @author Jim Wigginton <terrafrost@php.net>
  110. * @version 0.1.0
  111. * @access public
  112. * @package Net_SFTP
  113. */
  114. class Net_SFTP extends Net_SSH2 {
  115. /**
  116. * Packet Types
  117. *
  118. * @see Net_SFTP::Net_SFTP()
  119. * @var Array
  120. * @access private
  121. */
  122. var $packet_types = array();
  123. /**
  124. * Status Codes
  125. *
  126. * @see Net_SFTP::Net_SFTP()
  127. * @var Array
  128. * @access private
  129. */
  130. var $status_codes = array();
  131. /**
  132. * The Request ID
  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::_send_sftp_packet()
  139. * @access private
  140. */
  141. var $request_id = false;
  142. /**
  143. * The Packet Type
  144. *
  145. * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support
  146. * concurrent actions, so it's somewhat academic, here.
  147. *
  148. * @var Integer
  149. * @see Net_SFTP::_get_sftp_packet()
  150. * @access private
  151. */
  152. var $packet_type = -1;
  153. /**
  154. * Packet Buffer
  155. *
  156. * @var String
  157. * @see Net_SFTP::_get_sftp_packet()
  158. * @access private
  159. */
  160. var $packet_buffer = '';
  161. /**
  162. * Extensions supported by the server
  163. *
  164. * @var Array
  165. * @see Net_SFTP::_initChannel()
  166. * @access private
  167. */
  168. var $extensions = array();
  169. /**
  170. * Server SFTP version
  171. *
  172. * @var Integer
  173. * @see Net_SFTP::_initChannel()
  174. * @access private
  175. */
  176. var $version;
  177. /**
  178. * Current working directory
  179. *
  180. * @var String
  181. * @see Net_SFTP::_realpath()
  182. * @see Net_SFTP::chdir()
  183. * @access private
  184. */
  185. var $pwd = false;
  186. /**
  187. * Packet Type Log
  188. *
  189. * @see Net_SFTP::getLog()
  190. * @var Array
  191. * @access private
  192. */
  193. var $packet_type_log = array();
  194. /**
  195. * Packet Log
  196. *
  197. * @see Net_SFTP::getLog()
  198. * @var Array
  199. * @access private
  200. */
  201. var $packet_log = array();
  202. /**
  203. * Error information
  204. *
  205. * @see Net_SFTP::getSFTPErrors()
  206. * @see Net_SFTP::getLastSFTPError()
  207. * @var String
  208. * @access private
  209. */
  210. var $sftp_errors = array();
  211. /**
  212. * Directory Cache
  213. *
  214. * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or
  215. * rather than always
  216. *
  217. * @see Net_SFTP::_save_dir()
  218. * @see Net_SFTP::_remove_dir()
  219. * @see Net_SFTP::_is_dir()
  220. * @var Array
  221. * @access private
  222. */
  223. var $dirs = array();
  224. /**
  225. * Default Constructor.
  226. *
  227. * Connects to an SFTP server
  228. *
  229. * @param String $host
  230. * @param optional Integer $port
  231. * @param optional Integer $timeout
  232. * @return Net_SFTP
  233. * @access public
  234. */
  235. function Net_SFTP($host, $port = 22, $timeout = 10)
  236. {
  237. parent::Net_SSH2($host, $port, $timeout);
  238. $this->packet_types = array(
  239. 1 => 'NET_SFTP_INIT',
  240. 2 => 'NET_SFTP_VERSION',
  241. /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
  242. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
  243. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
  244. 3 => 'NET_SFTP_OPEN',
  245. 4 => 'NET_SFTP_CLOSE',
  246. 5 => 'NET_SFTP_READ',
  247. 6 => 'NET_SFTP_WRITE',
  248. 7 => 'NET_SFTP_LSTAT',
  249. 9 => 'NET_SFTP_SETSTAT',
  250. 11 => 'NET_SFTP_OPENDIR',
  251. 12 => 'NET_SFTP_READDIR',
  252. 13 => 'NET_SFTP_REMOVE',
  253. 14 => 'NET_SFTP_MKDIR',
  254. 15 => 'NET_SFTP_RMDIR',
  255. 16 => 'NET_SFTP_REALPATH',
  256. 17 => 'NET_SFTP_STAT',
  257. /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
  258. SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  259. pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
  260. 18 => 'NET_SFTP_RENAME',
  261. 101=> 'NET_SFTP_STATUS',
  262. 102=> 'NET_SFTP_HANDLE',
  263. /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
  264. SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
  265. pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
  266. 103=> 'NET_SFTP_DATA',
  267. 104=> 'NET_SFTP_NAME',
  268. 105=> 'NET_SFTP_ATTRS',
  269. 200=> 'NET_SFTP_EXTENDED'
  270. );
  271. $this->status_codes = array(
  272. 0 => 'NET_SFTP_STATUS_OK',
  273. 1 => 'NET_SFTP_STATUS_EOF',
  274. 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
  275. 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
  276. 4 => 'NET_SFTP_STATUS_FAILURE',
  277. 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
  278. 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
  279. 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
  280. 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
  281. 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
  282. 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
  283. 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
  284. 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
  285. 13 => 'NET_SFTP_STATUS_NO_MEDIA',
  286. 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
  287. 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
  288. 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
  289. 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
  290. 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
  291. 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
  292. 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
  293. 21 => 'NET_SFTP_STATUS_LINK_LOOP',
  294. 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
  295. 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
  296. 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
  297. 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
  298. 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
  299. 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
  300. 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
  301. 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
  302. 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
  303. 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
  304. );
  305. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
  306. // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
  307. $this->attributes = array(
  308. 0x00000001 => 'NET_SFTP_ATTR_SIZE',
  309. 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
  310. 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
  311. 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
  312. // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
  313. // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
  314. // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
  315. // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
  316. -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
  317. );
  318. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
  319. // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
  320. // the array for that $this->open5_flags and similarily alter the constant names.
  321. $this->open_flags = array(
  322. 0x00000001 => 'NET_SFTP_OPEN_READ',
  323. 0x00000002 => 'NET_SFTP_OPEN_WRITE',
  324. 0x00000004 => 'NET_SFTP_OPEN_APPEND',
  325. 0x00000008 => 'NET_SFTP_OPEN_CREATE',
  326. 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
  327. 0x00000020 => 'NET_SFTP_OPEN_EXCL'
  328. );
  329. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
  330. // see Net_SFTP::_parseLongname() for an explanation
  331. $this->file_types = array(
  332. 1 => 'NET_SFTP_TYPE_REGULAR',
  333. 2 => 'NET_SFTP_TYPE_DIRECTORY',
  334. 3 => 'NET_SFTP_TYPE_SYMLINK',
  335. 4 => 'NET_SFTP_TYPE_SPECIAL',
  336. 5 => 'NET_SFTP_TYPE_UNKNOWN',
  337. // the followin types were first defined for use in SFTPv5+
  338. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  339. 6 => 'NET_SFTP_TYPE_SOCKET',
  340. 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
  341. 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
  342. 9 => 'NET_SFTP_TYPE_FIFO'
  343. );
  344. $this->_define_array(
  345. $this->packet_types,
  346. $this->status_codes,
  347. $this->attributes,
  348. $this->open_flags,
  349. $this->file_types
  350. );
  351. }
  352. /**
  353. * Login
  354. *
  355. * @param String $username
  356. * @param optional String $password
  357. * @return Boolean
  358. * @access public
  359. */
  360. function login($username)
  361. {
  362. $args = func_get_args();
  363. if (!call_user_func_array(array('Net_SSH2', 'login'), $args)) {
  364. return false;
  365. }
  366. $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
  367. $packet = pack('CNa*N3',
  368. NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);
  369. if (!$this->_send_binary_packet($packet)) {
  370. return false;
  371. }
  372. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
  373. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  374. if ($response === false) {
  375. return false;
  376. }
  377. $packet = pack('CNNa*CNa*',
  378. NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
  379. if (!$this->_send_binary_packet($packet)) {
  380. return false;
  381. }
  382. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  383. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  384. if ($response === false) {
  385. // from PuTTY's psftp.exe
  386. $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
  387. "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
  388. "exec sftp-server";
  389. // 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
  390. // is redundant
  391. $packet = pack('CNNa*CNa*',
  392. NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('exec'), 'exec', 1, strlen($command), $command);
  393. if (!$this->_send_binary_packet($packet)) {
  394. return false;
  395. }
  396. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  397. $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  398. if ($response === false) {
  399. return false;
  400. }
  401. }
  402. $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
  403. if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
  404. return false;
  405. }
  406. $response = $this->_get_sftp_packet();
  407. if ($this->packet_type != NET_SFTP_VERSION) {
  408. user_error('Expected SSH_FXP_VERSION');
  409. return false;
  410. }
  411. extract(unpack('Nversion', $this->_string_shift($response, 4)));
  412. $this->version = $version;
  413. while (!empty($response)) {
  414. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  415. $key = $this->_string_shift($response, $length);
  416. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  417. $value = $this->_string_shift($response, $length);
  418. $this->extensions[$key] = $value;
  419. }
  420. /*
  421. SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
  422. however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
  423. not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
  424. one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
  425. 'newline@vandyke.com' would.
  426. */
  427. /*
  428. if (isset($this->extensions['newline@vandyke.com'])) {
  429. $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
  430. unset($this->extensions['newline@vandyke.com']);
  431. }
  432. */
  433. $this->request_id = 1;
  434. /*
  435. A Note on SFTPv4/5/6 support:
  436. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
  437. "If the client wishes to interoperate with servers that support noncontiguous version
  438. numbers it SHOULD send '3'"
  439. Given that the server only sends its version number after the client has already done so, the above
  440. seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the
  441. most popular.
  442. <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
  443. "If the server did not send the "versions" extension, or the version-from-list was not included, the
  444. server MAY send a status response describing the failure, but MUST then close the channel without
  445. processing any further requests."
  446. So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
  447. a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
  448. v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
  449. in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the
  450. channel and reopen it with a new and updated SSH_FXP_INIT packet.
  451. */
  452. switch ($this->version) {
  453. case 2:
  454. case 3:
  455. break;
  456. default:
  457. return false;
  458. }
  459. $this->pwd = $this->_realpath('.');
  460. $this->_save_dir($this->pwd);
  461. return true;
  462. }
  463. /**
  464. * Returns the current directory name
  465. *
  466. * @return Mixed
  467. * @access public
  468. */
  469. function pwd()
  470. {
  471. return $this->pwd;
  472. }
  473. /**
  474. * Logs errors
  475. *
  476. * @param String $response
  477. * @param optional Integer $status
  478. * @access public
  479. */
  480. function _logError($response, $status = -1)
  481. {
  482. if ($status == -1) {
  483. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  484. }
  485. $error = $this->status_codes[$status];
  486. if ($this->version > 2) {
  487. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  488. $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
  489. } else {
  490. $this->sftp_errors[] = $error;
  491. }
  492. }
  493. /**
  494. * Canonicalize the Server-Side Path Name
  495. *
  496. * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
  497. * the absolute (canonicalized) path.
  498. *
  499. * @see Net_SFTP::chdir()
  500. * @param String $path
  501. * @return Mixed
  502. * @access private
  503. */
  504. function _realpath($path)
  505. {
  506. if ($this->pwd === false) {
  507. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
  508. if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
  509. return false;
  510. }
  511. $response = $this->_get_sftp_packet();
  512. switch ($this->packet_type) {
  513. case NET_SFTP_NAME:
  514. // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
  515. // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
  516. // at is the first part and that part is defined the same in SFTP versions 3 through 6.
  517. $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
  518. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  519. return $this->_string_shift($response, $length);
  520. case NET_SFTP_STATUS:
  521. $this->_logError($response);
  522. return false;
  523. default:
  524. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  525. return false;
  526. }
  527. }
  528. if ($path[0] != '/') {
  529. $path = $this->pwd . '/' . $path;
  530. }
  531. $path = explode('/', $path);
  532. $new = array();
  533. foreach ($path as $dir) {
  534. if (!strlen($dir)) {
  535. continue;
  536. }
  537. switch ($dir) {
  538. case '..':
  539. array_pop($new);
  540. case '.':
  541. break;
  542. default:
  543. $new[] = $dir;
  544. }
  545. }
  546. return '/' . implode('/', $new);
  547. }
  548. /**
  549. * Changes the current directory
  550. *
  551. * @param String $dir
  552. * @return Boolean
  553. * @access public
  554. */
  555. function chdir($dir)
  556. {
  557. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  558. return false;
  559. }
  560. if ($dir[strlen($dir) - 1] != '/') {
  561. $dir.= '/';
  562. }
  563. $dir = $this->_realpath($dir);
  564. // confirm that $dir is, in fact, a valid directory
  565. if ($this->_is_dir($dir)) {
  566. $this->pwd = $dir;
  567. return true;
  568. }
  569. // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
  570. // the currently logged in user has the appropriate permissions or not. maybe you could see if
  571. // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
  572. // way to get those with SFTP
  573. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  574. return false;
  575. }
  576. // see Net_SFTP::nlist() for a more thorough explanation of the following
  577. $response = $this->_get_sftp_packet();
  578. switch ($this->packet_type) {
  579. case NET_SFTP_HANDLE:
  580. $handle = substr($response, 4);
  581. break;
  582. case NET_SFTP_STATUS:
  583. $this->_logError($response);
  584. return false;
  585. default:
  586. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  587. return false;
  588. }
  589. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  590. return false;
  591. }
  592. $response = $this->_get_sftp_packet();
  593. if ($this->packet_type != NET_SFTP_STATUS) {
  594. user_error('Expected SSH_FXP_STATUS');
  595. return false;
  596. }
  597. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  598. if ($status != NET_SFTP_STATUS_OK) {
  599. $this->_logError($response, $status);
  600. return false;
  601. }
  602. $this->_save_dir($dir);
  603. $this->pwd = $dir;
  604. return true;
  605. }
  606. /**
  607. * Returns a list of files in the given directory
  608. *
  609. * @param optional String $dir
  610. * @return Mixed
  611. * @access public
  612. */
  613. function nlist($dir = '.')
  614. {
  615. return $this->_list($dir, false);
  616. }
  617. /**
  618. * Returns a detailed list of files in the given directory
  619. *
  620. * @param optional String $dir
  621. * @return Mixed
  622. * @access public
  623. */
  624. function rawlist($dir = '.')
  625. {
  626. return $this->_list($dir, true);
  627. }
  628. /**
  629. * Reads a list, be it detailed or not, of files in the given directory
  630. *
  631. * $realpath exists because, in the case of the recursive deletes and recursive chmod's $realpath has already
  632. * been calculated.
  633. *
  634. * @param String $dir
  635. * @param optional Boolean $raw
  636. * @param optional Boolean $realpath
  637. * @return Mixed
  638. * @access private
  639. */
  640. function _list($dir, $raw = true, $realpath = true)
  641. {
  642. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  643. return false;
  644. }
  645. $dir = $this->_realpath($dir . '/');
  646. if ($dir === false) {
  647. return false;
  648. }
  649. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
  650. if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
  651. return false;
  652. }
  653. $response = $this->_get_sftp_packet();
  654. switch ($this->packet_type) {
  655. case NET_SFTP_HANDLE:
  656. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
  657. // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
  658. // represent the length of the string and leave it at that
  659. $handle = substr($response, 4);
  660. break;
  661. case NET_SFTP_STATUS:
  662. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  663. $this->_logError($response);
  664. return false;
  665. default:
  666. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  667. return false;
  668. }
  669. $this->_save_dir($dir);
  670. $contents = array();
  671. while (true) {
  672. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
  673. // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
  674. // SSH_MSG_CHANNEL_DATA messages is not known to me.
  675. if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
  676. return false;
  677. }
  678. $response = $this->_get_sftp_packet();
  679. switch ($this->packet_type) {
  680. case NET_SFTP_NAME:
  681. extract(unpack('Ncount', $this->_string_shift($response, 4)));
  682. for ($i = 0; $i < $count; $i++) {
  683. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  684. $shortname = $this->_string_shift($response, $length);
  685. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  686. $longname = $this->_string_shift($response, $length);
  687. $attributes = $this->_parseAttributes($response);
  688. if (!isset($attributes['type'])) {
  689. $fileType = $this->_parseLongname($longname);
  690. if ($fileType) {
  691. $attributes['type'] = $fileType;
  692. }
  693. }
  694. if (!$raw) {
  695. $contents[] = $shortname;
  696. } else {
  697. $contents[$shortname] = $attributes;
  698. }
  699. if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
  700. $this->_save_dir($dir . '/' . $shortname);
  701. }
  702. // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
  703. // final SSH_FXP_STATUS packet should tell us that, already.
  704. }
  705. break;
  706. case NET_SFTP_STATUS:
  707. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  708. if ($status != NET_SFTP_STATUS_EOF) {
  709. $this->_logError($response, $status);
  710. return false;
  711. }
  712. break 2;
  713. default:
  714. user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  715. return false;
  716. }
  717. }
  718. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  719. return false;
  720. }
  721. // "The client MUST release all resources associated with the handle regardless of the status."
  722. // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
  723. $response = $this->_get_sftp_packet();
  724. if ($this->packet_type != NET_SFTP_STATUS) {
  725. user_error('Expected SSH_FXP_STATUS');
  726. return false;
  727. }
  728. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  729. if ($status != NET_SFTP_STATUS_OK) {
  730. $this->_logError($response, $status);
  731. return false;
  732. }
  733. return $contents;
  734. }
  735. /**
  736. * Returns the file size, in bytes, or false, on failure
  737. *
  738. * Files larger than 4GB will show up as being exactly 4GB.
  739. *
  740. * @param String $filename
  741. * @return Mixed
  742. * @access public
  743. */
  744. function size($filename)
  745. {
  746. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  747. return false;
  748. }
  749. $filename = $this->_realpath($filename);
  750. if ($filename === false) {
  751. return false;
  752. }
  753. return $this->_size($filename);
  754. }
  755. /**
  756. * Save directories to cache
  757. *
  758. * @param String $dir
  759. * @access private
  760. */
  761. function _save_dir($dir)
  762. {
  763. // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($dir, '/'))
  764. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
  765. $temp = &$this->dirs;
  766. foreach ($dirs as $dir) {
  767. if (!isset($temp[$dir])) {
  768. $temp[$dir] = array();
  769. }
  770. $temp = &$temp[$dir];
  771. }
  772. }
  773. /**
  774. * Remove directories from cache
  775. *
  776. * @param String $dir
  777. * @access private
  778. */
  779. function _remove_dir($dir)
  780. {
  781. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
  782. $temp = &$this->dirs;
  783. foreach ($dirs as $dir) {
  784. if ($dir == end($dirs)) {
  785. unset($temp[$dir]);
  786. return true;
  787. }
  788. if (!isset($temp[$dir])) {
  789. return false;
  790. }
  791. $temp = &$temp[$dir];
  792. }
  793. }
  794. /**
  795. * Checks cache for directory
  796. *
  797. * Mainly used by chdir, which is, in turn, also used for determining whether or not an individual
  798. * file is a directory or not by stat() and lstat()
  799. *
  800. * @param String $dir
  801. * @access private
  802. */
  803. function _is_dir($dir)
  804. {
  805. $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir));
  806. $temp = &$this->dirs;
  807. foreach ($dirs as $dir) {
  808. if (!isset($temp[$dir])) {
  809. return false;
  810. }
  811. $temp = &$temp[$dir];
  812. }
  813. return true;
  814. }
  815. /**
  816. * Returns general information about a file.
  817. *
  818. * Returns an array on success and false otherwise.
  819. *
  820. * @param String $filename
  821. * @return Mixed
  822. * @access public
  823. */
  824. function stat($filename)
  825. {
  826. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  827. return false;
  828. }
  829. $filename = $this->_realpath($filename);
  830. if ($filename === false) {
  831. return false;
  832. }
  833. $stat = $this->_stat($filename, NET_SFTP_STAT);
  834. if ($stat === false) {
  835. return false;
  836. }
  837. if (isset($stat['type'])) {
  838. return $stat;
  839. }
  840. $pwd = $this->pwd;
  841. $stat['type'] = $this->chdir($filename) ?
  842. NET_SFTP_TYPE_DIRECTORY :
  843. NET_SFTP_TYPE_REGULAR;
  844. $this->pwd = $pwd;
  845. return $stat;
  846. }
  847. /**
  848. * Returns general information about a file or symbolic link.
  849. *
  850. * Returns an array on success and false otherwise.
  851. *
  852. * @param String $filename
  853. * @return Mixed
  854. * @access public
  855. */
  856. function lstat($filename)
  857. {
  858. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  859. return false;
  860. }
  861. $filename = $this->_realpath($filename);
  862. if ($filename === false) {
  863. return false;
  864. }
  865. $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
  866. if ($lstat === false) {
  867. return false;
  868. }
  869. if (isset($lstat['type'])) {
  870. return $lstat;
  871. }
  872. $stat = $this->_stat($filename, NET_SFTP_STAT);
  873. if ($lstat != $stat) {
  874. return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
  875. }
  876. $pwd = $this->pwd;
  877. $lstat['type'] = $this->chdir($filename) ?
  878. NET_SFTP_TYPE_DIRECTORY :
  879. NET_SFTP_TYPE_REGULAR;
  880. $this->pwd = $pwd;
  881. return $lstat;
  882. }
  883. /**
  884. * Returns general information about a file or symbolic link
  885. *
  886. * Determines information without calling Net_SFTP::_realpath().
  887. * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
  888. *
  889. * @param String $filename
  890. * @param Integer $type
  891. * @return Mixed
  892. * @access private
  893. */
  894. function _stat($filename, $type)
  895. {
  896. // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  897. $packet = pack('Na*', strlen($filename), $filename);
  898. if (!$this->_send_sftp_packet($type, $packet)) {
  899. return false;
  900. }
  901. $response = $this->_get_sftp_packet();
  902. switch ($this->packet_type) {
  903. case NET_SFTP_ATTRS:
  904. return $this->_parseAttributes($response);
  905. case NET_SFTP_STATUS:
  906. $this->_logError($response);
  907. return false;
  908. }
  909. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  910. return false;
  911. }
  912. /**
  913. * Returns the file size, in bytes, or false, on failure
  914. *
  915. * Determines the size without calling Net_SFTP::_realpath()
  916. *
  917. * @param String $filename
  918. * @return Mixed
  919. * @access private
  920. */
  921. function _size($filename)
  922. {
  923. $result = $this->_stat($filename, NET_SFTP_STAT);
  924. if ($result === false) {
  925. return false;
  926. }
  927. return isset($result['size']) ? $result['size'] : -1;
  928. }
  929. /**
  930. * Truncates a file to a given length
  931. *
  932. * @param String $filename
  933. * @param Integer $new_size
  934. * @return Boolean
  935. * @access public
  936. */
  937. function truncate($filename, $new_size)
  938. {
  939. $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 0x100000000, $new_size);
  940. return $this->_setstat($filename, $attr, false);
  941. }
  942. /**
  943. * Sets access and modification time of file.
  944. *
  945. * If the file does not exist, it will be created.
  946. *
  947. * @param String $filename
  948. * @param optional Integer $time
  949. * @param optional Integer $atime
  950. * @return Boolean
  951. * @access public
  952. */
  953. function touch($filename, $time = NULL, $atime = NULL)
  954. {
  955. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  956. return false;
  957. }
  958. $filename = $this->_realpath($filename);
  959. if ($filename === false) {
  960. return false;
  961. }
  962. if (!isset($time)) {
  963. $time = time();
  964. }
  965. if (!isset($atime)) {
  966. $atime = $time;
  967. }
  968. $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
  969. $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
  970. $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
  971. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  972. return false;
  973. }
  974. $response = $this->_get_sftp_packet();
  975. switch ($this->packet_type) {
  976. case NET_SFTP_HANDLE:
  977. $handle = substr($response, 4);
  978. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  979. return false;
  980. }
  981. $response = $this->_get_sftp_packet();
  982. if ($this->packet_type != NET_SFTP_STATUS) {
  983. user_error('Expected SSH_FXP_STATUS');
  984. return false;
  985. }
  986. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  987. if ($status != NET_SFTP_STATUS_OK) {
  988. $this->_logError($response, $status);
  989. return false;
  990. }
  991. return true;
  992. case NET_SFTP_STATUS:
  993. $this->_logError($response);
  994. break;
  995. default:
  996. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  997. return false;
  998. }
  999. return $this->_setstat($filename, $attr, false);
  1000. }
  1001. /**
  1002. * Changes file or directory owner
  1003. *
  1004. * Returns TRUE on success or FALSE on error.
  1005. *
  1006. * @param String $filename
  1007. * @param Integer $uid
  1008. * @param optional Boolean $recursive
  1009. * @return Boolean
  1010. * @access public
  1011. */
  1012. function chown($filename, $uid, $recursive = false)
  1013. {
  1014. // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
  1015. // "if the owner or group is specified as -1, then that ID is not changed"
  1016. $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);
  1017. return $this->_setstat($filename, $attr, $recursive);
  1018. }
  1019. /**
  1020. * Changes file or directory group
  1021. *
  1022. * Returns TRUE on success or FALSE on error.
  1023. *
  1024. * @param String $filename
  1025. * @param Integer $gid
  1026. * @param optional Boolean $recursive
  1027. * @return Boolean
  1028. * @access public
  1029. */
  1030. function chgrp($filename, $gid, $recursive = false)
  1031. {
  1032. $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);
  1033. return $this->_setstat($filename, $attr, $recursive);
  1034. }
  1035. /**
  1036. * Set permissions on a file.
  1037. *
  1038. * Returns the new file permissions on success or FALSE on error.
  1039. * If $recursive is true than this just returns TRUE or FALSE.
  1040. *
  1041. * @param Integer $mode
  1042. * @param String $filename
  1043. * @param optional Boolean $recursive
  1044. * @return Mixed
  1045. * @access public
  1046. */
  1047. function chmod($mode, $filename, $recursive = false)
  1048. {
  1049. if (is_string($mode) && is_int($filename)) {
  1050. $temp = $mode;
  1051. $mode = $filename;
  1052. $filename = $temp;
  1053. }
  1054. $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
  1055. if (!$this->_setstat($filename, $attr, $recursive)) {
  1056. return false;
  1057. }
  1058. if ($recursive) {
  1059. return true;
  1060. }
  1061. // rather than return what the permissions *should* be, we'll return what they actually are. this will also
  1062. // tell us if the file actually exists.
  1063. // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1064. $packet = pack('Na*', strlen($filename), $filename);
  1065. if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
  1066. return false;
  1067. }
  1068. $response = $this->_get_sftp_packet();
  1069. switch ($this->packet_type) {
  1070. case NET_SFTP_ATTRS:
  1071. $attrs = $this->_parseAttributes($response);
  1072. return $attrs['permissions'];
  1073. case NET_SFTP_STATUS:
  1074. $this->_logError($response);
  1075. return false;
  1076. }
  1077. user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  1078. return false;
  1079. }
  1080. /**
  1081. * Sets information about a file
  1082. *
  1083. * @param String $filename
  1084. * @param String $attr
  1085. * @param Boolean $recursive
  1086. * @return Boolean
  1087. * @access private
  1088. */
  1089. function _setstat($filename, $attr, $recursive)
  1090. {
  1091. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1092. return false;
  1093. }
  1094. $filename = $this->_realpath($filename);
  1095. if ($filename === false) {
  1096. return false;
  1097. }
  1098. if ($recursive) {
  1099. $i = 0;
  1100. $result = $this->_setstat_recursive($filename, $attr, $i);
  1101. $this->_read_put_responses($i);
  1102. return $result;
  1103. }
  1104. // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
  1105. // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
  1106. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
  1107. return false;
  1108. }
  1109. /*
  1110. "Because some systems must use separate system calls to set various attributes, it is possible that a failure
  1111. response will be returned, but yet some of the attributes may be have been successfully modified. If possible,
  1112. servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
  1113. -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
  1114. */
  1115. $response = $this->_get_sftp_packet();
  1116. if ($this->packet_type != NET_SFTP_STATUS) {
  1117. user_error('Expected SSH_FXP_STATUS');
  1118. return false;
  1119. }
  1120. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1121. if ($status != NET_SFTP_STATUS_OK) {
  1122. $this->_logError($response, $status);
  1123. return false;
  1124. }
  1125. return true;
  1126. }
  1127. /**
  1128. * Recursively sets information on directories on the SFTP server
  1129. *
  1130. * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  1131. *
  1132. * @param String $path
  1133. * @param String $attr
  1134. * @param Integer $i
  1135. * @return Boolean
  1136. * @access private
  1137. */
  1138. function _setstat_recursive($path, $attr, &$i)
  1139. {
  1140. if (!$this->_read_put_responses($i)) {
  1141. return false;
  1142. }
  1143. $i = 0;
  1144. $entries = $this->_list($path, true, false);
  1145. if ($entries === false) {
  1146. return $this->_setstat($path, $attr, false);
  1147. }
  1148. // normally $entries would have at least . and .. but it might not if the directories
  1149. // permissions didn't allow reading
  1150. if (empty($entries)) {
  1151. return false;
  1152. }
  1153. foreach ($entries as $filename=>$props) {
  1154. if ($filename == '.' || $filename == '..') {
  1155. continue;
  1156. }
  1157. if (!isset($props['type'])) {
  1158. return false;
  1159. }
  1160. $temp = $path . '/' . $filename;
  1161. if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1162. if (!$this->_setstat_recursive($temp, $attr, $i)) {
  1163. return false;
  1164. }
  1165. } else {
  1166. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
  1167. return false;
  1168. }
  1169. $i++;
  1170. if ($i >= 50) {
  1171. if (!$this->_read_put_responses($i)) {
  1172. return false;
  1173. }
  1174. $i = 0;
  1175. }
  1176. }
  1177. }
  1178. if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
  1179. return false;
  1180. }
  1181. $i++;
  1182. if ($i >= 50) {
  1183. if (!$this->_read_put_responses($i)) {
  1184. return false;
  1185. }
  1186. $i = 0;
  1187. }
  1188. return true;
  1189. }
  1190. /**
  1191. * Creates a directory.
  1192. *
  1193. * @param String $dir
  1194. * @return Boolean
  1195. * @access public
  1196. */
  1197. function mkdir($dir, $mode = -1, $recursive = false)
  1198. {
  1199. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1200. return false;
  1201. }
  1202. $dir = $this->_realpath($dir);
  1203. // by not providing any permissions, hopefully the server will use the logged in users umask - their
  1204. // default permissions.
  1205. $attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
  1206. if ($recursive) {
  1207. $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
  1208. if (empty($dirs[0])) {
  1209. array_shift($dirs);
  1210. $dirs[0] = '/' . $dirs[0];
  1211. }
  1212. for ($i = 0; $i < count($dirs); $i++) {
  1213. $temp = array_slice($dirs, 0, $i + 1);
  1214. $temp = implode('/', $temp);
  1215. $result = $this->_mkdir_helper($temp, $attr);
  1216. }
  1217. return $result;
  1218. }
  1219. return $this->_mkdir_helper($dir, $attr);
  1220. }
  1221. /**
  1222. * Helper function for directory creation
  1223. *
  1224. * @param String $dir
  1225. * @return Boolean
  1226. * @access private
  1227. */
  1228. function _mkdir_helper($dir, $attr)
  1229. {
  1230. if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
  1231. return false;
  1232. }
  1233. $response = $this->_get_sftp_packet();
  1234. if ($this->packet_type != NET_SFTP_STATUS) {
  1235. user_error('Expected SSH_FXP_STATUS');
  1236. return false;
  1237. }
  1238. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1239. if ($status != NET_SFTP_STATUS_OK) {
  1240. $this->_logError($response, $status);
  1241. return false;
  1242. }
  1243. $this->_save_dir($dir);
  1244. return true;
  1245. }
  1246. /**
  1247. * Removes a directory.
  1248. *
  1249. * @param String $dir
  1250. * @return Boolean
  1251. * @access public
  1252. */
  1253. function rmdir($dir)
  1254. {
  1255. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1256. return false;
  1257. }
  1258. $dir = $this->_realpath($dir);
  1259. if ($dir === false) {
  1260. return false;
  1261. }
  1262. if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
  1263. return false;
  1264. }
  1265. $response = $this->_get_sftp_packet();
  1266. if ($this->packet_type != NET_SFTP_STATUS) {
  1267. user_error('Expected SSH_FXP_STATUS');
  1268. return false;
  1269. }
  1270. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1271. if ($status != NET_SFTP_STATUS_OK) {
  1272. // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
  1273. $this->_logError($response, $status);
  1274. return false;
  1275. }
  1276. $this->_remove_dir($dir);
  1277. return true;
  1278. }
  1279. /**
  1280. * Uploads a file to the SFTP server.
  1281. *
  1282. * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
  1283. * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
  1284. * long, containing 'filename.ext' as its contents.
  1285. *
  1286. * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will
  1287. * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
  1288. * large $remote_file will be, as well.
  1289. *
  1290. * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
  1291. * care of that, yourself.
  1292. *
  1293. * As for $start... if it's negative (which it is by default) a new file will be created or an existing
  1294. * file truncated depending on $mode | NET_SFTP_RESUME. If it's zero or positive it'll be updated at that
  1295. * spot.
  1296. *
  1297. * @param String $remote_file
  1298. * @param String $data
  1299. * @param optional Integer $mode
  1300. * @param optional Integer $start
  1301. * @return Boolean
  1302. * @access public
  1303. * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
  1304. */
  1305. function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1)
  1306. {
  1307. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1308. return false;
  1309. }
  1310. $remote_file = $this->_realpath($remote_file);
  1311. if ($remote_file === false) {
  1312. return false;
  1313. }
  1314. $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
  1315. // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
  1316. // in practice, it doesn't seem to do that.
  1317. //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
  1318. // if NET_SFTP_OPEN_APPEND worked as it should the following (up until the -----------) wouldn't be necessary
  1319. $offset = 0;
  1320. if (($mode & NET_SFTP_RESUME) || $start >= 0) {
  1321. if ($start >= 0) {
  1322. $offset = $start;
  1323. } else {
  1324. $size = $this->_size($remote_file);
  1325. $offset = $size !== false ? $size : 0;
  1326. }
  1327. } else {
  1328. $flags|= NET_SFTP_OPEN_TRUNCATE;
  1329. }
  1330. // --------------
  1331. $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
  1332. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  1333. return false;
  1334. }
  1335. $response = $this->_get_sftp_packet();
  1336. switch ($this->packet_type) {
  1337. case NET_SFTP_HANDLE:
  1338. $handle = substr($response, 4);
  1339. break;
  1340. case NET_SFTP_STATUS:
  1341. $this->_logError($response);
  1342. return false;
  1343. default:
  1344. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  1345. return false;
  1346. }
  1347. $initialize = true;
  1348. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
  1349. if ($mode & NET_SFTP_LOCAL_FILE) {
  1350. if (!is_file($data)) {
  1351. user_error("$data is not a valid file");
  1352. return false;
  1353. }
  1354. $fp = @fopen($data, 'rb');
  1355. if (!$fp) {
  1356. return false;
  1357. }
  1358. $size = filesize($data);
  1359. } else {
  1360. $size = strlen($data);
  1361. }
  1362. $sent = 0;
  1363. $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
  1364. $sftp_packet_size = 4096; // PuTTY uses 4096
  1365. $i = 0;
  1366. while ($sent < $size) {
  1367. $temp = $mode & NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
  1368. $subtemp = $offset + $sent;
  1369. $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 0x100000000, $subtemp, strlen($temp), $temp);
  1370. if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
  1371. fclose($fp);
  1372. return false;
  1373. }
  1374. $sent+= strlen($temp);
  1375. $i++;
  1376. if ($i == 50) {
  1377. if (!$this->_read_put_responses($i)) {
  1378. $i = 0;
  1379. break;
  1380. }
  1381. $i = 0;
  1382. }
  1383. }
  1384. if (!$this->_read_put_responses($i)) {
  1385. return false;
  1386. }
  1387. if ($mode & NET_SFTP_LOCAL_FILE) {
  1388. fclose($fp);
  1389. }
  1390. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  1391. return false;
  1392. }
  1393. $response = $this->_get_sftp_packet();
  1394. if ($this->packet_type != NET_SFTP_STATUS) {
  1395. user_error('Expected SSH_FXP_STATUS');
  1396. return false;
  1397. }
  1398. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1399. if ($status != NET_SFTP_STATUS_OK) {
  1400. $this->_logError($response, $status);
  1401. return false;
  1402. }
  1403. return true;
  1404. }
  1405. /**
  1406. * Reads multiple successive SSH_FXP_WRITE responses
  1407. *
  1408. * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
  1409. * SSH_FXP_WRITEs, in succession, and then reading $i responses.
  1410. *
  1411. * @param Integer $i
  1412. * @return Boolean
  1413. * @access private
  1414. */
  1415. function _read_put_responses($i)
  1416. {
  1417. while ($i--) {
  1418. $response = $this->_get_sftp_packet();
  1419. if ($this->packet_type != NET_SFTP_STATUS) {
  1420. user_error('Expected SSH_FXP_STATUS');
  1421. return false;
  1422. }
  1423. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1424. if ($status != NET_SFTP_STATUS_OK) {
  1425. $this->_logError($response, $status);
  1426. break;
  1427. }
  1428. }
  1429. return $i < 0;
  1430. }
  1431. /**
  1432. * Downloads a file from the SFTP server.
  1433. *
  1434. * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
  1435. * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
  1436. * operation.
  1437. *
  1438. * $offset and $length can be used to download files in chunks.
  1439. *
  1440. * @param String $remote_file
  1441. * @param optional String $local_file
  1442. * @param optional Integer $offset
  1443. * @param optional Integer $length
  1444. * @return Mixed
  1445. * @access public
  1446. */
  1447. function get($remote_file, $local_file = false, $offset = 0, $length = -1)
  1448. {
  1449. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1450. return false;
  1451. }
  1452. $remote_file = $this->_realpath($remote_file);
  1453. if ($remote_file === false) {
  1454. return false;
  1455. }
  1456. $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
  1457. if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
  1458. return false;
  1459. }
  1460. $response = $this->_get_sftp_packet();
  1461. switch ($this->packet_type) {
  1462. case NET_SFTP_HANDLE:
  1463. $handle = substr($response, 4);
  1464. break;
  1465. case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1466. $this->_logError($response);
  1467. return false;
  1468. default:
  1469. user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  1470. return false;
  1471. }
  1472. if ($local_file !== false) {
  1473. $fp = fopen($local_file, 'wb');
  1474. if (!$fp) {
  1475. return false;
  1476. }
  1477. } else {
  1478. $content = '';
  1479. }
  1480. $size = (1 << 20) < $length || $length < 0 ? 1 << 20 : $length;
  1481. while (true) {
  1482. $packet = pack('Na*N3', strlen($handle), $handle, $offset / 0x100000000, $offset, $size);
  1483. if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
  1484. if ($local_file !== false) {
  1485. fclose($fp);
  1486. }
  1487. return false;
  1488. }
  1489. $response = $this->_get_sftp_packet();
  1490. switch ($this->packet_type) {
  1491. case NET_SFTP_DATA:
  1492. $temp = substr($response, 4);
  1493. $offset+= strlen($temp);
  1494. if ($local_file === false) {
  1495. $content.= $temp;
  1496. } else {
  1497. fputs($fp, $temp);
  1498. }
  1499. break;
  1500. case NET_SFTP_STATUS:
  1501. // could, in theory, return false if !strlen($content) but we'll hold off for the time being
  1502. $this->_logError($response);
  1503. break 2;
  1504. default:
  1505. user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS');
  1506. if ($local_file !== false) {
  1507. fclose($fp);
  1508. }
  1509. return false;
  1510. }
  1511. if ($length > 0 && $length <= $offset - $size) {
  1512. break;
  1513. }
  1514. }
  1515. if ($length > 0 && $length <= $offset - $size) {
  1516. if ($local_file === false) {
  1517. $content = substr($content, 0, $length);
  1518. } else {
  1519. ftruncate($fp, $length);
  1520. }
  1521. }
  1522. if ($local_file !== false) {
  1523. fclose($fp);
  1524. }
  1525. if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
  1526. return false;
  1527. }
  1528. $response = $this->_get_sftp_packet();
  1529. if ($this->packet_type != NET_SFTP_STATUS) {
  1530. user_error('Expected SSH_FXP_STATUS');
  1531. return false;
  1532. }
  1533. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1534. if ($status != NET_SFTP_STATUS_OK) {
  1535. $this->_logError($response, $status);
  1536. return false;
  1537. }
  1538. // if $content isn't set that means a file was written to
  1539. if (isset($content)) {
  1540. return $content;
  1541. }
  1542. return true;
  1543. }
  1544. /**
  1545. * Deletes a file on the SFTP server.
  1546. *
  1547. * @param String $path
  1548. * @param Boolean $recursive
  1549. * @return Boolean
  1550. * @access public
  1551. */
  1552. function delete($path, $recursive = true)
  1553. {
  1554. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1555. return false;
  1556. }
  1557. $path = $this->_realpath($path);
  1558. if ($path === false) {
  1559. return false;
  1560. }
  1561. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  1562. if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
  1563. return false;
  1564. }
  1565. $response = $this->_get_sftp_packet();
  1566. if ($this->packet_type != NET_SFTP_STATUS) {
  1567. user_error('Expected SSH_FXP_STATUS');
  1568. return false;
  1569. }
  1570. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1571. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1572. if ($status != NET_SFTP_STATUS_OK) {
  1573. $this->_logError($response, $status);
  1574. if (!$recursive) {
  1575. return false;
  1576. }
  1577. $i = 0;
  1578. $result = $this->_delete_recursive($path, $i);
  1579. $this->_read_put_responses($i);
  1580. return $result;
  1581. }
  1582. return true;
  1583. }
  1584. /**
  1585. * Recursively deletes directories on the SFTP server
  1586. *
  1587. * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  1588. *
  1589. * @param String $path
  1590. * @param Integer $i
  1591. * @return Boolean
  1592. * @access private
  1593. */
  1594. function _delete_recursive($path, &$i)
  1595. {
  1596. if (!$this->_read_put_responses($i)) {
  1597. return false;
  1598. }
  1599. $i = 0;
  1600. $entries = $this->_list($path, true, false);
  1601. // normally $entries would have at least . and .. but it might not if the directories
  1602. // permissions didn't allow reading
  1603. if (empty($entries)) {
  1604. return false;
  1605. }
  1606. foreach ($entries as $filename=>$props) {
  1607. if ($filename == '.' || $filename == '..') {
  1608. continue;
  1609. }
  1610. if (!isset($props['type'])) {
  1611. return false;
  1612. }
  1613. $temp = $path . '/' . $filename;
  1614. if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1615. if (!$this->_delete_recursive($temp, $i)) {
  1616. return false;
  1617. }
  1618. } else {
  1619. if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
  1620. return false;
  1621. }
  1622. $i++;
  1623. if ($i >= 50) {
  1624. if (!$this->_read_put_responses($i)) {
  1625. return false;
  1626. }
  1627. $i = 0;
  1628. }
  1629. }
  1630. }
  1631. if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
  1632. return false;
  1633. }
  1634. $this->_remove_dir($path);
  1635. $i++;
  1636. if ($i >= 50) {
  1637. if (!$this->_read_put_responses($i)) {
  1638. return false;
  1639. }
  1640. $i = 0;
  1641. }
  1642. return true;
  1643. }
  1644. /**
  1645. * Renames a file or a directory on the SFTP server
  1646. *
  1647. * @param String $oldname
  1648. * @param String $newname
  1649. * @return Boolean
  1650. * @access public
  1651. */
  1652. function rename($oldname, $newname)
  1653. {
  1654. if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) {
  1655. return false;
  1656. }
  1657. $oldname = $this->_realpath($oldname);
  1658. $newname = $this->_realpath($newname);
  1659. if ($oldname === false || $newname === false) {
  1660. return false;
  1661. }
  1662. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  1663. $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
  1664. if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
  1665. return false;
  1666. }
  1667. $response = $this->_get_sftp_packet();
  1668. if ($this->packet_type != NET_SFTP_STATUS) {
  1669. user_error('Expected SSH_FXP_STATUS');
  1670. return false;
  1671. }
  1672. // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  1673. extract(unpack('Nstatus', $this->_string_shift($response, 4)));
  1674. if ($status != NET_SFTP_STATUS_OK) {
  1675. $this->_logError($response, $status);
  1676. return false;
  1677. }
  1678. return true;
  1679. }
  1680. /**
  1681. * Parse Attributes
  1682. *
  1683. * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
  1684. *
  1685. * @param String $response
  1686. * @return Array
  1687. * @access private
  1688. */
  1689. function _parseAttributes(&$response)
  1690. {
  1691. $attr = array();
  1692. extract(unpack('Nflags', $this->_string_shift($response, 4)));
  1693. // SFTPv4+ have a type field (a byte) that follows the above flag field
  1694. foreach ($this->attributes as $key => $value) {
  1695. switch ($flags & $key) {
  1696. case NET_SFTP_ATTR_SIZE: // 0x00000001
  1697. // size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
  1698. // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
  1699. // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
  1700. // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
  1701. // 4GB as being 4GB.
  1702. extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
  1703. $attr['size'] = $upper ? 0x100000000 * $upper : 0;
  1704. $attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
  1705. break;
  1706. case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
  1707. $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
  1708. break;
  1709. case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
  1710. $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
  1711. // mode == permissions; permissions was the original array key and is retained for bc purposes.
  1712. // mode was added because that's the more industry standard terminology
  1713. $attr+= array('mode' => $attr['permissions']);
  1714. $fileType = $this->_parseMode($attr['permissions']);
  1715. if ($fileType !== false) {
  1716. $attr+= array('type' => $fileType);
  1717. }
  1718. break;
  1719. case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
  1720. $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
  1721. break;
  1722. case NET_SFTP_ATTR_EXTENDED: // 0x80000000
  1723. extract(unpack('Ncount', $this->_string_shift($response, 4)));
  1724. for ($i = 0; $i < $count; $i++) {
  1725. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1726. $key = $this->_string_shift($response, $length);
  1727. extract(unpack('Nlength', $this->_string_shift($response, 4)));
  1728. $attr[$key] = $this->_string_shift($response, $length);
  1729. }
  1730. }
  1731. }
  1732. return $attr;
  1733. }
  1734. /**
  1735. * Attempt to identify the file type
  1736. *
  1737. * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
  1738. *
  1739. * @param Integer $mode
  1740. * @return Integer
  1741. * @access private
  1742. */
  1743. function _parseMode($mode)
  1744. {
  1745. // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
  1746. // see, also, http://linux.die.net/man/2/stat
  1747. switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
  1748. case 0000000: // no file type specified - figure out the file type using alternative means
  1749. return false;
  1750. case 0040000:
  1751. return NET_SFTP_TYPE_DIRECTORY;
  1752. case 0100000:
  1753. return NET_SFTP_TYPE_REGULAR;
  1754. case 0120000:
  1755. return NET_SFTP_TYPE_SYMLINK;
  1756. // new types introduced in SFTPv5+
  1757. // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  1758. case 0010000: // named pipe (fifo)
  1759. return NET_SFTP_TYPE_FIFO;
  1760. case 0020000: // character special
  1761. return NET_SFTP_TYPE_CHAR_DEVICE;
  1762. case 0060000: // block special
  1763. return NET_SFTP_BLOCK_DEVICE;
  1764. case 0140000: // socket
  1765. return NET_SFTP_TYPE_SOCKET;
  1766. case 0160000: // whiteout
  1767. // "SPECIAL should be used for files that are of
  1768. // a known type which cannot be expressed in the protocol"
  1769. return NET_SFTP_TYPE_SPECIAL;
  1770. default:
  1771. return NET_SFTP_TYPE_UNKNOWN;
  1772. }
  1773. }
  1774. /**
  1775. * Parse Longname
  1776. *
  1777. * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open
  1778. * a file as a directory and see if an error is returned or you could try to parse the
  1779. * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does.
  1780. * The result is returned using the
  1781. * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
  1782. *
  1783. * If the longname is in an unrecognized format bool(false) is returned.
  1784. *
  1785. * @param String $longname
  1786. * @return Mixed
  1787. * @access private
  1788. */
  1789. function _parseLongname($longname)
  1790. {
  1791. // http://en.wikipedia.org/wiki/Unix_file_types
  1792. // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
  1793. if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
  1794. switch ($longname[0]) {
  1795. case '-':
  1796. return NET_SFTP_TYPE_REGULAR;
  1797. case 'd':
  1798. return NET_SFTP_TYPE_DIRECTORY;
  1799. case 'l':
  1800. return NET_SFTP_TYPE_SYMLINK;
  1801. default:
  1802. return NET_SFTP_TYPE_SPECIAL;
  1803. }
  1804. }
  1805. return false;
  1806. }
  1807. /**
  1808. * Sends SFTP Packets
  1809. *
  1810. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  1811. *
  1812. * @param Integer $type
  1813. * @param String $data
  1814. * @see Net_SFTP::_get_sftp_packet()
  1815. * @see Net_SSH2::_send_channel_packet()
  1816. * @return Boolean
  1817. * @access private
  1818. */
  1819. function _send_sftp_packet($type, $data)
  1820. {
  1821. $packet = $this->request_id !== false ?
  1822. pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
  1823. pack('NCa*', strlen($data) + 1, $type, $data);
  1824. $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  1825. $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet);
  1826. $stop = strtok(microtime(), ' ') + strtok('');
  1827. if (defined('NET_SFTP_LOGGING')) {
  1828. $packet_type = '-> ' . $this->packet_types[$type] .
  1829. ' (' . round($stop - $start, 4) . 's)';
  1830. if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
  1831. echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
  1832. flush();
  1833. ob_flush();
  1834. } else {
  1835. $this->packet_type_log[] = $packet_type;
  1836. if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  1837. $this->packet_log[] = $data;
  1838. }
  1839. }
  1840. }
  1841. return $result;
  1842. }
  1843. /**
  1844. * Receives SFTP Packets
  1845. *
  1846. * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  1847. *
  1848. * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
  1849. * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
  1850. * messages containing one SFTP packet.
  1851. *
  1852. * @see Net_SFTP::_send_sftp_packet()
  1853. * @return String
  1854. * @access private
  1855. */
  1856. function _get_sftp_packet()
  1857. {
  1858. $this->curTimeout = false;
  1859. $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  1860. // SFTP packet length
  1861. while (strlen($this->packet_buffer) < 4) {
  1862. $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  1863. if (is_bool($temp)) {
  1864. $this->packet_type = false;
  1865. $this->packet_buffer = '';
  1866. return false;
  1867. }
  1868. $this->packet_buffer.= $temp;
  1869. }
  1870. extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
  1871. $tempLength = $length;
  1872. $tempLength-= strlen($this->packet_buffer);
  1873. // SFTP packet type and data payload
  1874. while ($tempLength > 0) {
  1875. $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL);
  1876. if (is_bool($temp)) {
  1877. $this->packet_type = false;
  1878. $this->packet_buffer = '';
  1879. return false;
  1880. }
  1881. $this->packet_buffer.= $temp;
  1882. $tempLength-= strlen($temp);
  1883. }
  1884. $stop = strtok(microtime(), ' ') + strtok('');
  1885. $this->packet_type = ord($this->_string_shift($this->packet_buffer));
  1886. if ($this->request_id !== false) {
  1887. $this->_string_shift($this->packet_buffer, 4); // remove the request id
  1888. $length-= 5; // account for the request id and the packet type
  1889. } else {
  1890. $length-= 1; // account for the packet type
  1891. }
  1892. $packet = $this->_string_shift($this->packet_buffer, $length);
  1893. if (defined('NET_SFTP_LOGGING')) {
  1894. $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
  1895. ' (' . round($stop - $start, 4) . 's)';
  1896. if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
  1897. echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
  1898. flush();
  1899. ob_flush();
  1900. } else {
  1901. $this->packet_type_log[] = $packet_type;
  1902. if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  1903. $this->packet_log[] = $packet;
  1904. }
  1905. }
  1906. }
  1907. return $packet;
  1908. }
  1909. /**
  1910. * Returns a log of the packets that have been sent and received.
  1911. *
  1912. * 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')
  1913. *
  1914. * @access public
  1915. * @return String or Array
  1916. */
  1917. function getSFTPLog()
  1918. {
  1919. if (!defined('NET_SFTP_LOGGING')) {
  1920. return false;
  1921. }
  1922. switch (NET_SFTP_LOGGING) {
  1923. case NET_SFTP_LOG_COMPLEX:
  1924. return $this->_format_log($this->packet_log, $this->packet_type_log);
  1925. break;
  1926. //case NET_SFTP_LOG_SIMPLE:
  1927. default:
  1928. return $this->packet_type_log;
  1929. }
  1930. }
  1931. /**
  1932. * Returns all errors
  1933. *
  1934. * @return String
  1935. * @access public
  1936. */
  1937. function getSFTPErrors()
  1938. {
  1939. return $this->sftp_errors;
  1940. }
  1941. /**
  1942. * Returns the last error
  1943. *
  1944. * @return String
  1945. * @access public
  1946. */
  1947. function getLastSFTPError()
  1948. {
  1949. return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
  1950. }
  1951. /**
  1952. * Get supported SFTP versions
  1953. *
  1954. * @return Array
  1955. * @access public
  1956. */
  1957. function getSupportedVersions()
  1958. {
  1959. $temp = array('version' => $this->version);
  1960. if (isset($this->extensions['versions'])) {
  1961. $temp['extensions'] = $this->extensions['versions'];
  1962. }
  1963. return $temp;
  1964. }
  1965. /**
  1966. * Disconnect
  1967. *
  1968. * @param Integer $reason
  1969. * @return Boolean
  1970. * @access private
  1971. */
  1972. function _disconnect($reason)
  1973. {
  1974. $this->pwd = false;
  1975. parent::_disconnect($reason);
  1976. }
  1977. }