PageRenderTime 63ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 1ms

/code/classes/Daemon/FTPd/Client.class.php

https://github.com/blekkzor/pinetd2
PHP | 797 lines | 635 code | 106 blank | 56 comment | 126 complexity | 3ae00f7d77f3f7fa15f774c1862ea63f MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * \file Daemon/FTPd/Client.class.php
  4. * \brief FTP daemon core file - client side
  5. */
  6. namespace Daemon\FTPd;
  7. use pinetd\Logger;
  8. use pinetd\SUID;
  9. /**
  10. * \brief Our FTPd client class
  11. */
  12. class Client extends \pinetd\TCP\Client {
  13. private $login = null; /*!< login info */
  14. private $binary = false; /*!< default = ASCII mode */
  15. private $mode = null;
  16. private $noop = 0; /*!< noop counter */
  17. private $resume = 0; /*!< resume RECV or STOR */
  18. private $tmp_login = null;
  19. private $rnfr = null; /*!< RENAME FROM (RNFR) state */
  20. protected $fs;
  21. function __construct($fd, $peer, $parent, $protocol) {
  22. parent::__construct($fd, $peer, $parent, $protocol);
  23. $this->setMsgEnd("\r\n");
  24. }
  25. /**
  26. * \brief Tell the user to wait for resolve
  27. */
  28. function welcomeUser() {
  29. $this->sendMsg('220-Looking up your hostname...');
  30. return true; // returning false will close client
  31. }
  32. /**
  33. * \brief Send a nice header message
  34. */
  35. function sendBanner() {
  36. $this->sendMsg('220-Welcome to SimpleFTPd v2.0 by MagicalTux <mark@gg.st>');
  37. list($cur, $max) = $this->IPC->getUserCount();
  38. $this->sendMsg('220-You are user '.$cur.' on a maximum of '.$max.' users');
  39. $this->sendMsg('220 You are '.$this->getHostName().', connected to '.$this->IPC->getName());
  40. // let's get a filesystem
  41. $class = relativeclass($this, 'Filesystem');
  42. $this->fs = new $class();
  43. return true;
  44. }
  45. protected function setProcessStatus($msg = '') {
  46. if ($msg == '') $msg = 'idle';
  47. if (is_null($this->login)) return parent::setProcessStatus('(not logged in) ' . $msg);
  48. return parent::setProcessStatus('('.$this->login.':'.$this->fs->getCwd().') '.$msg);
  49. }
  50. /**
  51. * \brief Called on shutdown signal (daemon is stopping)
  52. */
  53. function shutdown() {
  54. $this->sendMsg('500 Server is closing this link. Please reconnect later.');
  55. }
  56. /**
  57. * \brief Returns true if a file is writable
  58. */
  59. protected function canWriteFile($fil) {
  60. if (is_null($this->login)) return false;
  61. if ($this->login == 'ftp') return false;
  62. return $this->fs->isWritable($fil);
  63. }
  64. /**
  65. * \brief Check if the provided login/pass pair is correct. This is forwarded to Daemon\FTPd\Base
  66. */
  67. protected function checkAccess($login, $pass) {
  68. return $this->IPC->checkAccess($login, $pass, $this->peer);
  69. }
  70. /**
  71. * Called when size used on FTP may have changed. If increment is provided, this function SHOULD NOT compute the size of the whole FTP, and instead increment used quota by $increment after having checked if it was OK
  72. */
  73. protected function updateQuota($increment = null) {
  74. // noop
  75. return true;
  76. }
  77. /**
  78. * \brief Clear current xfer mode, closing any open socket if needed
  79. */
  80. protected function clearXferMode() {
  81. if (is_null($this->mode)) return;
  82. if ($this->mode['type'] == 'pasv') {
  83. fclose($this->mode['sock']);
  84. }
  85. $this->mode = null;
  86. }
  87. /**
  88. * \brief initiate a xfer protocol (PORT or PASV) for future transmission
  89. */
  90. protected function initiateXfer() {
  91. if (is_null($this->mode)) return false;
  92. switch($this->mode['type']) {
  93. case 'pasv':
  94. $sock = @stream_socket_accept($this->mode['sock'], 30);
  95. if (!$sock) return false;
  96. fclose($this->mode['sock']);
  97. $this->mode = null;
  98. return $sock;
  99. case 'port':
  100. $sock = @stream_socket_client('tcp://'.$this->mode['ip'].':'.$this->mode['port'], $errno, $errstr, 30);
  101. if (!$sock) {
  102. $this->log(Logger::LOG_WARN, 'Could not connect to peer tcp://'.$this->mode['ip'].':'.$this->mode['port'].' in PORT mode: ['.$errno.'] '.$errstr);
  103. return false;
  104. }
  105. $this->mode = null;
  106. return $sock;
  107. }
  108. return false;
  109. }
  110. /**
  111. * \brief "Command not found" handler
  112. */
  113. function _cmd_default($argv) {
  114. $this->sendMsg('502 Command '.$argv[0].' unknown!!');
  115. $this->log(Logger::LOG_DEBUG, 'UNKNOWN COMMAND: '.implode(' ', $argv));
  116. }
  117. function _cmd_quit() {
  118. $this->sendMsg('221 Good bye!');
  119. $this->close();
  120. $this->updateQuota();
  121. }
  122. function _cmd_allo() {
  123. return $this->_cmd_noop();
  124. }
  125. function _cmd_noop() {
  126. $this->sendMsg('200 Waitin\' for ya orders!');
  127. }
  128. function _cmd_user($argv) {
  129. if (!is_null($this->login)) {
  130. $this->sendMsg('500 Already logged in');
  131. return;
  132. }
  133. $login = $argv[1];
  134. if ((($login == 'ftp') || ($login == 'anonymous') || ($login == 'anon')) &&
  135. ($root = $this->IPC->getAnonymousRoot())) {
  136. // check for max anonymous
  137. list($cur, $max) = $this->IPC->getAnonymousCount();
  138. if ($cur >= $max) {
  139. $this->sendMsg('421 Too many anonymous users logged in, please try again later!');
  140. return;
  141. }
  142. if (!$root) {
  143. $this->sendMsg('500 Anonymous FTP access is disabled on this server');
  144. return;
  145. }
  146. $SUID = $this->IPC->canSUID();
  147. if ($SUID) $SUID = new SUID($SUID['User'], $SUID['Group']);
  148. if (!$this->fs->setRoot($root, $this->IPC->canChroot())) {
  149. $this->sendMsg('500 An error occured while trying to access anonymous root');
  150. $this->log(Logger::LOG_ERR, 'chroot() failed for anonymous login in '.$root);
  151. return;
  152. }
  153. if ($SUID) {
  154. if (!$SUID->setIt()) {
  155. $this->sendMsg('500 An error occured while trying to access anonymous root');
  156. $this->log(Logger::LOG_ERR, 'setuid()/setgid() failed for anonymous login');
  157. // we most likely already chroot()ed, can't return at this point
  158. $this->close();
  159. $this->IPC->killSelf($this->fd);
  160. return;
  161. }
  162. }
  163. $this->login = 'ftp'; // keyword for "anonymous"
  164. $this->IPC->setLoggedIn($this->fd, $this->login);
  165. $this->sendMsg('230 Anonymous user logged in, welcome!');
  166. return;
  167. }
  168. $this->tmp_login = $login;
  169. $this->sendMsg('331 Please provide password for user '.$login);
  170. }
  171. function _cmd_pass($argv) {
  172. if ($this->login == 'ftp') {
  173. $this->sendMsg('230 No password required for anonymous');
  174. return;
  175. }
  176. if (!is_null($this->login)) {
  177. $this->sendMsg('500 Already logged in!');
  178. return;
  179. }
  180. if (is_null($this->tmp_login)) {
  181. $this->sendMsg('530 Please send USER before sending PASS');
  182. return;
  183. }
  184. $login = $this->tmp_login;
  185. $pass = $argv[1];
  186. $res = $this->checkAccess($login, $pass);
  187. if ((!$res) || (!isset($res['root']))) {
  188. sleep(4); // TODO: This may cause a DoS if running without fork, detect case and act (if $this->IPC isa IPC)
  189. $this->sendMsg('530 Login or password may be invalid, please check again');
  190. return;
  191. }
  192. $root = $res['root'];
  193. $SUID = $this->IPC->canSUID();
  194. if ($SUID) {
  195. $user = null;
  196. $group = null;
  197. if (isset($res['user'])) $user = $res['user'];
  198. if (isset($res['group'])) $group = $res['group'];
  199. if (is_null($user)) $user = $SUID['User'];
  200. if (is_null($group)) $group = $SUID['Group'];
  201. $SUID = new SUID($user, $group);
  202. } elseif (isset($res['user'])) {
  203. $this->sendMsg('500 An error occured, please contact system administrator and try again later');
  204. $this->log(Logger::LOG_ERR, 'Could not SUID while SUID is required by underlying auth mechanism while logging in user '.$login);
  205. return;
  206. }
  207. if (!$this->fs->setRoot($root, $this->IPC->canChroot())) {
  208. $this->sendMsg('500 An error occured, please contact system administrator and try again later');
  209. $this->log(Logger::LOG_ERR, 'chroot() failed for login '.$login.' in '.$root);
  210. return;
  211. }
  212. if ($SUID) {
  213. if (!$SUID->setIt()) {
  214. $this->sendMsg('500 An error occured, please contact system administrator and try again later');
  215. $this->log(Logger::LOG_ERR, 'setuid()/setgid() failed for login '.$login);
  216. // we most likely already chroot()ed, can't turn back at this point
  217. $this->close();
  218. $this->IPC->killSelf($this->fd);
  219. return;
  220. }
  221. }
  222. $this->login = $login;
  223. $this->IPC->setLoggedIn($this->fd, $this->login);
  224. if (isset($res['banner'])) {
  225. foreach($res['banner'] as $lin) $this->sendMsg('230-'.$lin);
  226. }
  227. $this->sendMsg('230 Login success, welcome to your FTP');
  228. if (isset($res['chdir']))
  229. $this->fs->chDir($res['chdir']);
  230. $this->fs->setOptions($res);
  231. $this->updateQuota();
  232. }
  233. function _cmd_syst() {
  234. if (is_null($this->login)) {
  235. $this->sendMsg('530 Please login first!');
  236. return;
  237. }
  238. $this->sendMsg('215 UNIX Type: L8');
  239. }
  240. function _cmd_type($argv) {
  241. if (is_null($this->login)) {
  242. $this->sendMsg('530 Please login first!');
  243. return;
  244. }
  245. switch(strtoupper($argv[1])) {
  246. case 'A':
  247. $this->binary = false;
  248. $this->sendMsg('200 TYPE set to ASCII');
  249. return;
  250. case 'I':
  251. $this->binary = true;
  252. $this->sendMsg('200 TYPE set to BINARY');
  253. return;
  254. default:
  255. $this->sendMsg('501 You can only use TYPE A or TYPE I');
  256. }
  257. }
  258. function _cmd_pwd() {
  259. if (is_null($this->login)) {
  260. $this->sendMsg('530 Please login first!');
  261. return;
  262. }
  263. $cwd = $this->fs->getCwd();
  264. $this->sendMsg('257 "'.$cwd.'" is your Current Working Directory');
  265. }
  266. function _cmd_cdup($argv) {
  267. $this->_cmd_cwd(array('CWD', '..'), 'CWD', '..');
  268. }
  269. function _cmd_cwd($argv, $cmd, $fullarg) {
  270. if (is_null($this->login)) {
  271. $this->sendMsg('530 Please login first!');
  272. return;
  273. }
  274. // new path in $fullarg
  275. if (!$this->fs->chDir($fullarg)) {
  276. $this->sendMsg('500 Couldn\'t change location');
  277. } else {
  278. $this->sendMsg('250 Directory changed');
  279. }
  280. }
  281. function _cmd_rest($argv) {
  282. if (is_null($this->login)) {
  283. $this->sendMsg('530 Please login first!');
  284. return;
  285. }
  286. $this->restore = (int)$argv[1];
  287. $this->sendMsg('350 Restarting at '.$this->restore);
  288. }
  289. function _cmd_port($argv) {
  290. if (is_null($this->login)) {
  291. $this->sendMsg('530 Please login first!');
  292. return;
  293. }
  294. // INPUT : a,b,c,d,p1,p2 (all & 0xff)
  295. // IP: a.b.c.d
  296. // PORT: p1 | (p2 << 8)
  297. $data = explode(',', $argv[1]);
  298. if (count($data) != 6) {
  299. $this->sendMsg('500 Invalid PORT command, should be a,b,c,d,e,f');
  300. return;
  301. }
  302. foreach($data as &$val) $val = ((int)$val) & 0xff;
  303. $ip = $data[0].'.'.$data[1].'.'.$data[2].'.'.$data[3]; // original ip
  304. $port = $data[5] | ($data[4] << 8);
  305. if ($port < 1024) { // SAFETY CHECK
  306. $this->sendMsg('500 Invalid PORT command (port < 1024)');
  307. return;
  308. }
  309. if ($ip != $this->peer[0]) {
  310. if ($this->login == 'ftp') {
  311. $this->sendMsg('500 FXP denied to anonymous users');
  312. return;
  313. }
  314. $this->sendMsg('200-FXP initialized to '.$ip);
  315. }
  316. $this->clearXferMode();
  317. $this->mode = array(
  318. 'type' => 'port',
  319. 'ip' => $ip,
  320. 'port' => $port,
  321. );
  322. $this->sendMsg('200 PORT command successful');
  323. }
  324. // TODO: http://www.faqs.org/rfcs/rfc2428.html
  325. function _cmd_eprt($argv) {
  326. if (is_null($this->login)) {
  327. $this->sendMsg('530 Please login first!');
  328. return;
  329. }
  330. // INPUT : |num|ip|port|
  331. // IP: a.b.c.d
  332. $data = explode('|', $argv[1]);
  333. if (count($data) != 5) {
  334. $this->sendMsg('500 Invalid EPRT command, should be |1|ip|port|');
  335. return;
  336. }
  337. $proto = $data[1];
  338. $ip = $data[2];
  339. $port = $data[3];
  340. if ($port < 1024) { // SAFETY CHECK
  341. $this->sendMsg('500 Invalid PORT command (port < 1024)');
  342. return;
  343. }
  344. if ($ip != $this->peer[0]) {
  345. if ($this->login == 'ftp') {
  346. $this->sendMsg('500 FXP denied to anonymous users');
  347. return;
  348. }
  349. $this->sendMsg('200-FXP initialized to '.$ip);
  350. }
  351. $this->clearXferMode();
  352. $this->mode = array(
  353. 'type' => 'port',
  354. 'ip' => $ip,
  355. 'port' => $port,
  356. 'proto' => $proto,
  357. );
  358. $this->sendMsg('200 EPRT command successful');
  359. }
  360. function _cmd_pasv() {
  361. if (is_null($this->login)) {
  362. $this->sendMsg('530 Please login first!');
  363. return;
  364. }
  365. // get pasv_ip
  366. $pasv_ip = $this->IPC->getPasvIP();
  367. list($real_ip) = explode(':', stream_socket_get_name($this->fd, false));
  368. if (is_null($pasv_ip)) $pasv_ip = $real_ip;
  369. $sock = @stream_socket_server('tcp://'.$real_ip.':0', $errno, $errstr); // thanks god, php made this so easy
  370. if (!$sock) {
  371. $this->sendMsg('500 Couldn\'t create passive socket');
  372. $this->log(Logger::LOG_WARN, 'Could not create FTP PASV socket on tcp://'.$pasv_ip.':0 - ['.$errno.'] '.$errstr);
  373. return;
  374. }
  375. list(, $port) = explode(':', stream_socket_get_name($sock, false)); // fetch auto-assigned port
  376. $data = str_replace('.', ',', $pasv_ip);
  377. $data .= ',' . (($port >> 8) & 0xff);
  378. $data .= ',' . ($port & 0xff);
  379. $this->sendMsg('227 Entering passive mode ('.$data.')');
  380. $this->clearXferMode();
  381. $this->mode = array(
  382. 'type' => 'pasv',
  383. 'sock' => $sock,
  384. 'ip' => $pasv_ip,
  385. 'port' => $port,
  386. );
  387. }
  388. function _cmd_nlst($argv, $cmd, $fullarg) {
  389. $argv[0] = 'NLST';
  390. return $this->_cmd_list($argv, $cmd, $fullarg);
  391. }
  392. function _cmd_list($argv, $cmd, $fullarg) {
  393. if (is_null($this->login)) {
  394. $this->sendMsg('530 Please login first!');
  395. return;
  396. }
  397. // TODO: Implement handling of options to list
  398. if ($fullarg[0] == '-') {
  399. // parameters passed? (-l, -la, -m, etc...)
  400. $pos = strpos($fullarg, ' ');
  401. if ($pos === false) {
  402. $fullarg = '';
  403. } else {
  404. $fullarg = ltrim(substr($fullarg, $pos+1));
  405. }
  406. }
  407. $list = $this->fs->listDir($fullarg);
  408. if (is_null($list)) {
  409. $this->sendMsg('500 LIST: Directory not found or too many symlink levels');
  410. return;
  411. }
  412. $sock = $this->initiateXfer();
  413. if (!$sock) {
  414. $this->sendMsg('500 Unable to initiate connection, please provide PORT/PASV, and make sure your firewall is configured correctly');
  415. return;
  416. }
  417. list($ip, $port) = explode(':', stream_socket_get_name($sock, true));
  418. if (($this->login == 'ftp') && ($this->peer[0] != $ip)) {
  419. fclose($sock);
  420. $this->sendMsg('500 FXP unallowed for anonymous users!');
  421. return;
  422. }
  423. $this->sendMsg('150 Connection with '.$ip.':'.$port.' is established');
  424. if (!$list) {
  425. fclose($sock);
  426. $this->sendMsg('226 Transmission complete');
  427. return;
  428. }
  429. if ($argv[0] == 'NLST') {
  430. foreach($list as $fdata) {
  431. fputs($sock, $fdata['name']."\r\n");
  432. }
  433. } else {
  434. foreach($list as $fdata) {
  435. $flag = $fdata['flags'];
  436. list($year, $month, $day, $hour, $mins) = explode('|', date('Y|M|d|H|i', $fdata['mtime']));
  437. // $timeline: same year: "HH:SS". Other: " YYYY" (%5d)
  438. if ($year == date('Y')) {
  439. $timeline = sprintf('%02d:%02d', $hour, $mins);
  440. } else {
  441. $timeline = sprintf('%05d', $year);
  442. }
  443. $res = sprintf('%s %4u %-8d %-8d %8u %s %2d %s %s',
  444. $flag,
  445. 1, /* TODO: nlinks */
  446. 0, /* owner id */
  447. 0, /* group id */
  448. $fdata['size'], /* size */
  449. $month, /* month name */
  450. $day,
  451. $timeline,
  452. $fdata['name']
  453. );
  454. if (isset($fdata['link'])) {
  455. // read the link
  456. $res.=" -> ".$fdata['link'];
  457. }
  458. fputs($sock, $res."\r\n");
  459. }
  460. }
  461. $this->sendMsg('226 Transmission complete');
  462. fclose($sock);
  463. }
  464. function _cmd_retr($argv, $cmd, $fullarg) {
  465. if (is_null($this->login)) {
  466. $this->sendMsg('530 Please login first!');
  467. return;
  468. }
  469. $resume = $this->restore;
  470. $this->restore = 0;
  471. $info = $this->fs->open($fullarg, false, $resume);
  472. if (!$info) {
  473. $this->sendMsg('500 RETR: File not found or too many symlink levels');
  474. return;
  475. }
  476. $fp = $info['fp'];
  477. $size = $info['size'];
  478. $size -= $resume;
  479. $this->setProcessStatus(strtoupper($cmd[0]).' '.$fil);
  480. $sock = $this->initiateXfer();
  481. if (!$sock) {
  482. $this->sendMsg('500 Unable to initiate connection, please provide PORT/PASV, and make sure your firewall is configured correctly');
  483. $this->fs->close($fp);
  484. return;
  485. }
  486. list($ip, $port) = explode(':', stream_socket_get_name($sock, true));
  487. if (($this->login == 'ftp') && ($this->peer[0] != $ip)) {
  488. fclose($sock);
  489. $this->fs->close($fp);
  490. $this->sendMsg('500 FXP unallowed for anonymous users!');
  491. return;
  492. }
  493. $this->sendMsg('150 '.$size.' bytes to send');
  494. // transmit file
  495. $res = stream_copy_to_stream($fp, $sock);
  496. if ($res != $size) {
  497. $this->sendMsg('500 Xfer connection closed!');
  498. } else {
  499. $this->sendMsg('226 Send terminated');
  500. }
  501. fclose($sock);
  502. $this->fs->close($fp);
  503. }
  504. function _cmd_appe($argv, $cmd, $fullarg) {
  505. $argv[0] = 'APPE';
  506. return $this->_cmd_stor($argv, 'APPE', $fullarg);
  507. }
  508. function _cmd_stor($argv, $cmd, $fullarg) {
  509. $appe = ($argv[0] == 'APPE');
  510. if (is_null($this->login)) {
  511. $this->sendMsg('530 Please login first!');
  512. return;
  513. }
  514. if (!$appe) {
  515. $resume = $this->restore;
  516. $this->restore = 0;
  517. } else {
  518. $resume = -1;
  519. }
  520. $info = $this->fs->open($fullarg, true, $resume);
  521. if (!$info) {
  522. $this->sendMsg('500 Failed to open file for writing');
  523. return;
  524. }
  525. $fp = $info['fp'];
  526. $this->setProcessStatus(strtoupper($cmd[0]).' '.$fil);
  527. // initiate link
  528. $sock = $this->initiateXfer();
  529. if (!$sock) {
  530. $this->sendMsg('500 Unable to initiate connection, please provide PORT/PASV, and make sure your firewall is configured correctly');
  531. $this->fs->close($fp);
  532. return;
  533. }
  534. list($ip, $port) = explode(':', stream_socket_get_name($sock, true));
  535. if (($this->login == 'ftp') && ($this->peer[0] != $ip)) {
  536. fclose($sock);
  537. $this->fs->close($fp);
  538. $this->sendMsg('500 FXP unallowed for anonymous users!');
  539. return;
  540. }
  541. $this->sendMsg('150 Ready for data stream, from '.$ip.':'.$port);
  542. stream_set_blocking($sock, true);
  543. $bytes = 0;
  544. while(1) {
  545. $data = fread($sock, 65535);
  546. if ($data === '') break;
  547. if ($data === false) break;
  548. if (!$this->updateQuota(strlen($data))) {
  549. $this->sendMsg('552 Quota exceed!');
  550. break;
  551. }
  552. fwrite($fp, $data);
  553. $bytes += strlen($data);
  554. // $bytes += stream_copy_to_stream($sock, $fp);
  555. }
  556. $this->sendMsg('226 '.$bytes.' bytes written');
  557. fclose($sock);
  558. $this->fs->close($fp);
  559. $this->updateQuota();
  560. }
  561. function _cmd_site($argv) {
  562. if (is_null($this->login)) {
  563. $this->sendMsg('530 Please login first!');
  564. return;
  565. }
  566. switch(strtolower($argv[1])) {
  567. case 'chmod':
  568. $this->sendMsg('200 Chmod not supported');
  569. return;
  570. // TODO: implement SITE MD5 and SITE SHA1
  571. default:
  572. $this->sendMsg('501 unknown SITE command');
  573. return;
  574. }
  575. }
  576. function _cmd_rmd($argv, $cmd, $fullarg) {
  577. $argv[0] = 'RMD';
  578. return $this->_cmd_dele($argv, $cmd, $fullarg);
  579. }
  580. function _cmd_rrmd($argv, $cmd, $fullarg) {
  581. $argv[0] = 'RRMD';
  582. return $this->_cmd_dele($argv, $cmd, $fullarg);
  583. }
  584. function _cmd_dele($argv, $cmd, $fullarg) {
  585. // DELETE A file (unlink)
  586. if (is_null($this->login)) {
  587. $this->sendMsg('530 Please login first!');
  588. return;
  589. }
  590. switch($argv[0]) {
  591. case 'RRMD':
  592. $this->fs->doRecursiveRMD($fullarg);
  593. break;
  594. case 'RMD':
  595. $this->fs->rmDir($fullarg);
  596. break;
  597. default:
  598. $this->fs->unLink($fullarg);
  599. }
  600. if ($this->fs->fileExists($fullarg)) {
  601. $this->sendMsg('500 Operation failed');
  602. return;
  603. }
  604. $this->sendMsg('226 Entry removed');
  605. $this->updateQuota();
  606. }
  607. function _cmd_mkd($argv, $cmd, $fullarg) {
  608. if (is_null($this->login)) {
  609. $this->sendMsg('530 Please login first!');
  610. return;
  611. }
  612. $fil = $fullarg;
  613. if (!$this->fs->mkDir($fil)) {
  614. $this->sendMsg('500 MKD failed');
  615. return;
  616. }
  617. $this->sendMsg('257 Directory created');
  618. $this->updateQuota();
  619. }
  620. function _cmd_size($argv, $cmd, $fullarg) {
  621. if (is_null($this->login)) {
  622. $this->sendMsg('530 Please login first!');
  623. return;
  624. }
  625. $size = $this->fs->size($fullarg);
  626. if ($size === false) {
  627. $this->sendMsg('550 File not found or too many symlink levels');
  628. return;
  629. }
  630. $this->sendMsg('213 '.$size);
  631. }
  632. function _cmd_rnfr($argv, $cmd, $fullarg) {
  633. if (is_null($this->login)) {
  634. $this->sendMsg('530 Please login first!');
  635. return;
  636. }
  637. $this->sendMsg('350 Please provide new name...');
  638. $this->rnfr = $fullarg;
  639. }
  640. function _cmd_rnto($argv, $cmd, $fullarg) {
  641. if (is_null($this->login)) {
  642. $this->sendMsg('530 Please login first!');
  643. return;
  644. }
  645. if (is_null($this->rnfr)) {
  646. $this->sendMsg('500 please start with RNFR');
  647. return;
  648. }
  649. if (!$this->fs->rename($this->rnfr, $fullarg)) {
  650. $this->sendMsg('500 Rename failed');
  651. return;
  652. }
  653. $this->sendMsg('250 Rename successful');
  654. $this->updateQuota();
  655. }
  656. function _cmd_mode($argv) {
  657. if ($argv[1] != 'S') {
  658. $this->sendMsg('504 Onmy mode S(stream) is supported.');
  659. return;
  660. }
  661. $this->sendMsg('200 S OK');
  662. }
  663. function _cmd_stru($argv) {
  664. if ($argv[1] != 'F') {
  665. $this->sendMsg('504 Only STRU F(file) is supported.');
  666. return;
  667. }
  668. $this->sendMsg('200 F OK');
  669. }
  670. function _cmd_mdtm($argv, $cmd, $fullarg) {
  671. if (is_null($this->login)) {
  672. $this->sendMsg('530 Please login first!');
  673. return;
  674. }
  675. $stat = $this->fs->stat($fullarg);
  676. if (!$stat) {
  677. $this->sendMsg('500 File not found or too many symlink levels');
  678. return;
  679. }
  680. $this->sendMsg('213 '.date('YmdHis', $stat['mtime']));
  681. }
  682. function _cmd_feat() {
  683. $this->sendMsg('211-Extensions supported:');
  684. $this->sendMsg('211-MDTM');
  685. $this->sendMsg('211-SIZE');
  686. $this->sendMsg('211-REST STREAM');
  687. $this->sendMsg('211-PASV');
  688. $this->sendMsg('211-UTF8');
  689. $this->sendMsg('211 End.');
  690. }
  691. }