server.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <?php
  2. $socket = null;
  3. $errno = 0;
  4. $context = stream_context_create(array('ssl' => array('local_cert' => dirname(__FILE__).'/cert.pem')));
  5. $socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
  6. if (!$socket) {
  7. echo "$errstr ($errno)\n";
  8. die("could not start/bind the ftp server\n");
  9. }
  10. $socket_name = stream_socket_get_name($socket, false);
  11. $port = (int) substr($socket_name, strrpos($socket_name, ':') + 1);
  12. $pid = pcntl_fork();
  13. if ($pid) {
  14. function dump_and_exit($buf)
  15. {
  16. var_dump($buf);
  17. exit;
  18. }
  19. function anonymous()
  20. {
  21. return $GLOBALS['user'] === 'anonymous';
  22. }
  23. /* quick&dirty realpath() like function */
  24. function change_dir($dir)
  25. {
  26. global $cwd;
  27. if ($dir[0] == '/') {
  28. $cwd = $dir;
  29. return;
  30. }
  31. $cwd = "$cwd/$dir";
  32. do {
  33. $old = $cwd;
  34. $cwd = preg_replace('@/?[^/]+/\.\.@', '', $cwd);
  35. } while ($old != $cwd);
  36. $cwd = strtr($cwd, array('//' => '/'));
  37. if (!$cwd) $cwd = '/';
  38. }
  39. $s = stream_socket_accept($socket);
  40. if (!$s) die("Error accepting a new connection\n");
  41. register_shutdown_function(function() use($pid, $s) {
  42. fclose($s);
  43. pcntl_waitpid($pid, $status);
  44. });
  45. fputs($s, "220----- PHP FTP server 0.3 -----\r\n220 Service ready\r\n");
  46. $buf = fread($s, 2048);
  47. function user_auth($buf) {
  48. global $user, $s, $ssl, $bug37799;
  49. if (!empty($ssl)) {
  50. if ($buf !== "AUTH TLS\r\n") {
  51. fputs($s, "500 Syntax error, command unrecognized.\r\n");
  52. dump_and_exit($buf);
  53. }
  54. if (empty($bug37799)) {
  55. fputs($s, "234 auth type accepted\r\n");
  56. } else {
  57. fputs($s, "666 dummy\r\n");
  58. sleep(1);
  59. fputs($s, "666 bogus msg\r\n");
  60. exit;
  61. }
  62. if (!stream_socket_enable_crypto($s, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER)) {
  63. die("SSLv23 handshake failed.\n");
  64. }
  65. if (!preg_match('/^PBSZ \d+\r\n$/', $buf = fread($s, 2048))) {
  66. fputs($s, "501 bogus data\r\n");
  67. dump_and_exit($buf);
  68. }
  69. fputs($s, "200 OK\r\n");
  70. $buf = fread($s, 2048);
  71. if ($buf !== "PROT P\r\n") {
  72. fputs($s, "504 Wrong protection.\r\n");
  73. dump_and_exit($buf);
  74. }
  75. fputs($s, "200 OK\r\n");
  76. $buf = fread($s, 2048);
  77. }
  78. if ($buf == "AUTH TLS\r\n") {
  79. fputs($s, "500 not supported.\r\n");
  80. return ;
  81. } else if (!preg_match('/^USER (\w+)\r\n$/', $buf, $m)) {
  82. fputs($s, "500 Syntax error, command unrecognized.\r\n");
  83. dump_and_exit($buf);
  84. }
  85. $user = $m[1];
  86. if ($user !== 'user' && $user !== 'anonymous') {
  87. fputs($s, "530 Not logged in.\r\n");
  88. exit;
  89. }
  90. if (anonymous()) {
  91. fputs($s, "230 Anonymous user logged in\r\n");
  92. } else {
  93. fputs($s, "331 User name ok, need password\r\n");
  94. if (!preg_match('/^PASS (\w+)\r\n$/', $buf = fread($s, 100), $m)) {
  95. fputs($s, "500 Syntax error, command unrecognized.\r\n");
  96. dump_and_exit($buf);
  97. }
  98. $pass = $m[1];
  99. if ($pass === 'pass') {
  100. fputs($s, "230 User logged in\r\n");
  101. } else {
  102. fputs($s, "530 Not logged in.\r\n");
  103. exit;
  104. }
  105. }
  106. }
  107. user_auth($buf);
  108. $cwd = '/';
  109. $num_bogus_cmds = 0;
  110. while($buf = fread($s, 4098)) {
  111. if (!empty($bogus)) {
  112. fputs($s, "502 Command not implemented (".$num_bogus_cmds++.").\r\n");
  113. } else if ($buf === "HELP\r\n") {
  114. fputs($s, "214-There is help available for the following commands:\r\n");
  115. fputs($s, " USER\r\n");
  116. fputs($s, " HELP\r\n");
  117. fputs($s, "214 end of list\r\n");
  118. } elseif ($buf === "HELP HELP\r\n") {
  119. fputs($s, "214 Syntax: HELP [<SP> <string>] <CRLF>\r\n");
  120. } elseif ($buf === "PWD\r\n") {
  121. fputs($s, "257 \"$cwd\" is current directory.\r\n");
  122. } elseif ($buf === "CDUP\r\n") {
  123. change_dir('..');
  124. fputs($s, "250 CDUP command successful.\r\n");
  125. } elseif ($buf === "SYST\r\n") {
  126. if (isset($bug27809)) {
  127. fputs($s, "215 OS/400 is the remote operating system. The TCP/IP version is \"V5R2M0\"\r\n");
  128. } elseif (isset($bug79100)) {
  129. // do nothing so test hits timeout
  130. } elseif (isset($bug80901)) {
  131. fputs($s, "\r\n" . str_repeat("*", 4096) . "\r\n");
  132. } else {
  133. fputs($s, "215 UNIX Type: L8.\r\n");
  134. }
  135. } elseif ($buf === "TYPE A\r\n") {
  136. $ascii = true;
  137. fputs($s, "200 OK\r\n");
  138. } elseif ($buf === "AUTH SSL\r\n") {
  139. $ascii = true;
  140. fputs($s, "500 not supported\r\n");
  141. } elseif ($buf === "TYPE I\r\n") {
  142. $ascii = false;
  143. fputs($s, "200 OK\r\n");
  144. } elseif ($buf === "QUIT\r\n") {
  145. break;
  146. } elseif (preg_match("~^PORT (\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\r\n$~", $buf, $m)) {
  147. $host = "$m[1].$m[2].$m[3].$m[4]";
  148. $port = ((int)$m[5] << 8) + (int)$m[6];
  149. fputs($s, "200 OK.\r\n");
  150. } elseif (preg_match("~^STOR ([\w/.-]+)\r\n$~", $buf, $m)) {
  151. fputs($s, "150 File status okay; about to open data connection\r\n");
  152. if(empty($pasv))
  153. {
  154. if (!$fs = stream_socket_client("tcp://$host:$port")) {
  155. fputs($s, "425 Can't open data connection\r\n");
  156. continue;
  157. }
  158. $data = stream_get_contents($fs);
  159. $orig = file_get_contents(dirname(__FILE__).'/'.$m[1]);
  160. if (isset($ascii) && !$ascii && $orig === $data) {
  161. fputs($s, "226 Closing data Connection.\r\n");
  162. } elseif ((!empty($ascii) || isset($bug39583)) && $data === strtr($orig, array("\r\n" => "\n", "\r" => "\n", "\n" => "\r\n"))) {
  163. fputs($s, "226 Closing data Connection.\r\n");
  164. } else {
  165. var_dump($data);
  166. var_dump($orig);
  167. fputs($s, "552 Requested file action aborted.\r\n");
  168. }
  169. fclose($fs);
  170. }else{
  171. $data = file_get_contents('nm2.php');
  172. $orig = file_get_contents(dirname(__FILE__).'/'.$m[1]);
  173. if ( $orig === $data) {
  174. fputs($s, "226 Closing data Connection.\r\n");
  175. } else {
  176. var_dump($data);
  177. var_dump($orig);
  178. fputs($s, "552 Requested file action aborted.\r\n");
  179. }
  180. }
  181. } elseif (preg_match("~^APPE ([\w/.-]+)\r\n$~", $buf, $m)) {
  182. fputs($s, "150 File status okay; about to open data connection\r\n");
  183. if(empty($pasv))
  184. {
  185. if (!$fs = stream_socket_client("tcp://$host:$port")) {
  186. fputs($s, "425 Can't open data connection\r\n");
  187. continue;
  188. }
  189. $data = stream_get_contents($fs);
  190. file_put_contents(__DIR__.'/'.$m[1], $data, FILE_APPEND);
  191. fputs($s, "226 Closing data Connection.\r\n");
  192. fclose($fs);
  193. }else{
  194. $data = stream_get_contents($fs);
  195. file_put_contents(__DIR__.'/'.$m[1], $data, FILE_APPEND);
  196. fputs($s, "226 Closing data Connection.\r\n");
  197. fclose($fs);
  198. }
  199. }elseif (preg_match("~^CWD ([A-Za-z./]+)\r\n$~", $buf, $m)) {
  200. if (isset($bug77680)) {
  201. fputs($s, "550 Directory change to $m[1] failed: file does not exist\r\n");
  202. var_dump($buf);
  203. } else {
  204. change_dir($m[1]);
  205. fputs($s, "250 CWD command successful.\r\n");
  206. }
  207. } elseif (preg_match("~^NLST(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) {
  208. if (isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) {
  209. fputs($s, "250 $m[1]: No such file or directory\r\n");
  210. continue;
  211. }
  212. // there are some servers that don't open the ftp-data socket if there's nothing to send
  213. if (isset($bug39458) && isset($m[1]) && $m[1] === 'emptydir') {
  214. fputs($s, "226 Transfer complete.\r\n");
  215. continue;
  216. }
  217. if (empty($pasv)) {
  218. fputs($s, "150 File status okay; about to open data connection\r\n");
  219. if (!$fs = stream_socket_client("tcp://$host:$port")) {
  220. fputs($s, "425 Can't open data connection\r\n");
  221. continue;
  222. }
  223. } else {
  224. fputs($s, "125 Data connection already open; transfer starting.\r\n");
  225. $fs=$pasvs;
  226. }
  227. if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) {
  228. die("SSLv23 handshake failed.\n");
  229. }
  230. if (empty($m[1]) || $m[1] !== 'emptydir') {
  231. fputs($fs, "file1\r\nfile1\r\nfile\nb0rk\r\n");
  232. }
  233. fputs($s, "226 Closing data Connection.\r\n");
  234. fclose($fs);
  235. } elseif (preg_match("~^MKD ([A-Za-z./]+)\r\n$~", $buf, $m)) {
  236. if (isset($bug7216)) {
  237. fputs($s, "257 OK.\r\n");
  238. } else {
  239. if (isset($bug77680)) {
  240. var_dump($buf);
  241. }
  242. fputs($s, "257 \"/path/to/ftproot$cwd$m[1]\" created.\r\n");
  243. }
  244. } elseif (preg_match('/^USER /', $buf)) {
  245. user_auth($buf);
  246. } elseif (preg_match('/^MDTM ([\w\h]+)/', $buf, $matches)) {
  247. switch ($matches [1]){
  248. case "A":
  249. fputs($s, "213 19980615100045.014\r\n");
  250. break;
  251. case "B":
  252. fputs($s, "213 19980615100045.014\r\n");
  253. break;
  254. case "C":
  255. fputs($s, "213 19980705132316\r\n");
  256. break;
  257. case "19990929043300 File6":
  258. fputs($s, "213 19991005213102\r\n");
  259. break;
  260. default :
  261. fputs($s, "550 No file named \"{$matches [1]}\"\r\n");
  262. break;
  263. }
  264. }elseif (preg_match('/^RETR ([\/]*[\w\h]+)/', $buf, $matches)) {
  265. if(!empty($pasv)){
  266. ;
  267. }
  268. else if (!$fs = stream_socket_client("tcp://$host:$port")) {
  269. fputs($s, "425 Can't open data connection\r\n");
  270. continue;
  271. }
  272. switch($matches[1]){
  273. case "pasv":
  274. fputs($s, "150 File status okay; about to open data connection.\r\n");
  275. //the data connection is handled in another forked process
  276. // called from outside this while loop
  277. fputs($s, "226 Closing data Connection.\r\n");
  278. break;
  279. case "a story":
  280. fputs($s, "150 File status okay; about to open data connection.\r\n");
  281. fputs($fs, "For sale: baby shoes, never worn.\r\n");
  282. fputs($s, "226 Closing data Connection.\r\n");
  283. break;
  284. case "binary data":
  285. fputs($s, "150 File status okay; about to open data connection.\r\n");
  286. $transfer_type = $ascii? 'ASCII' : 'BINARY' ;
  287. fputs($fs, $transfer_type."Foo\0Bar\r\n");
  288. fputs($s, "226 Closing data Connection.\r\n");
  289. break;
  290. case "fget":
  291. fputs($s, "150 File status okay; about to open data connection.\r\n");
  292. $transfer_type = $ascii? 'ASCII' : 'BINARY' ;
  293. fputs($fs, $transfer_type."FooBar\r\n");
  294. fputs($s, "226 Closing data Connection.\r\n");
  295. break;
  296. case "fgetresume":
  297. fputs($s, "150 File status okay; about to open data connection.\r\n");
  298. $transfer_type = $ascii? 'ASCII' : 'BINARY' ;
  299. fputs($fs, "Bar\r\n");
  300. fputs($s, "226 Closing data Connection.\r\n");
  301. break;
  302. case "fget_large":
  303. fputs($s, "150 File status okay; about to open data connection.\r\n");
  304. $transfer_type = $ascii? 'ASCII' : 'BINARY' ;
  305. if ($GLOBALS['rest_pos'] == '5368709119') {
  306. fputs($fs, "X");
  307. } else {
  308. fputs($fs, "Y");
  309. }
  310. fputs($s, "226 Closing data Connection.\r\n");
  311. break;
  312. case "mediumfile":
  313. fputs($s, "150 File status okay; about to open data connection.\r\n");
  314. for($i = 0; $i < 150; $i++){
  315. fputs($fs, "This is line $i of the test data.\n");
  316. }
  317. fputs($s, "226 Closing data Connection.\r\n");
  318. break;
  319. case "/bug73457":
  320. fputs($s, "150 File status okay; about to open data connection.\r\n");
  321. break;
  322. default:
  323. fputs($s, "550 {$matches[1]}: No such file or directory \r\n");
  324. break;
  325. }
  326. if(isset($fs))
  327. fclose($fs);
  328. }elseif (preg_match('/^PASV/', $buf, $matches)) {
  329. $pasv=true;
  330. $host = "127.0.0.1";
  331. $i=0;
  332. if (empty($bug73457)) {
  333. if (!empty($ssl)) {
  334. $soc = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
  335. } else {
  336. $soc = stream_socket_server("tcp://127.0.0.1:0");
  337. }
  338. if (!$soc) {
  339. echo "$errstr ($errno)\n";
  340. die("could not bind passive port\n");
  341. }
  342. $soc_name = stream_socket_get_name($soc, false);
  343. $pasv_port = (int) substr($soc_name, strrpos($soc_name, ':') + 1);
  344. } else {
  345. $pasv_port = 1234;
  346. }
  347. $p2 = $pasv_port % ((int) 1 << 8);
  348. $p1 = ($pasv_port-$p2)/((int) 1 << 8);
  349. fputs($s, "227 Entering Passive Mode. (127,0,0,1,{$p1},{$p2})\r\n");
  350. if (empty($bug73457)) {
  351. $pasvs = stream_socket_accept($soc,10);
  352. }
  353. } elseif (preg_match('/^EPSV/', $buf, $matches)) {
  354. fputs($s, "550 Extended passsive mode not supported.\r\n");
  355. } elseif (preg_match('/^SITE EXEC/', $buf, $matches)) {
  356. fputs($s, "200 OK\r\n");
  357. } elseif (preg_match('/^RMD/', $buf, $matches)) {
  358. fputs($s, "250 OK\r\n");
  359. } elseif (preg_match('/^SITE CHMOD/', $buf, $matches)) {
  360. fputs($s, "200 OK\r\n");
  361. } elseif (preg_match('/^DELE ([\w\h]+)/', $buf, $matches)) {
  362. if (isset($matches[1]) && in_array($matches[1], ['file1', "file\nb0rk"])){
  363. fputs($s, "250 Delete successful\r\n");
  364. } else {
  365. fputs($s, "550 No such file or directory\r\n");
  366. }
  367. } elseif (preg_match('/^ALLO (\d+)/', $buf, $matches)) {
  368. fputs($s, "200 " . $matches[1] . " bytes allocated\r\n");
  369. }elseif (preg_match('/^LIST www\//', $buf, $matches)) {
  370. fputs($s, "226 Transfer complete\r\n");
  371. }elseif (preg_match('/^LIST no_exists\//', $buf, $matches)) {
  372. fputs($s, "425 Error establishing connection\r\n");
  373. }elseif (preg_match('/^REST (\d+)/', $buf, $matches)) {
  374. $GLOBALS['rest_pos'] = $matches[1];
  375. fputs($s, "350 OK\r\n");
  376. }elseif (preg_match('/^SIZE largefile/', $buf)) {
  377. fputs($s, "213 5368709120\r\n");
  378. }elseif (preg_match('/^RNFR existing_file/', $buf, $matches)) {
  379. fputs($s, "350 File or directory exists, ready for destination name\r\n");
  380. }elseif (preg_match('/^RNFR nonexisting_file/', $buf, $matches)) {
  381. fputs($s, "550 No such file or directory\r\n");
  382. }elseif (preg_match('/^RNTO nonexisting_file/', $buf, $matches)) {
  383. fputs($s, "250 Rename successful\r\n");
  384. }elseif (preg_match('/^MLSD no_exists\//', $buf, $matches)) {
  385. fputs($s, "425 Error establishing connection\r\n");
  386. }elseif (preg_match("~^MLSD(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) {
  387. if(isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) {
  388. fputs($s, "250 $m[1]: No such file or directory\r\n");
  389. continue;
  390. }
  391. // there are some servers that don't open the ftp-data socket if there's nothing to send
  392. if(isset($bug39458) && isset($m[1]) && $m[1] === 'emptydir') {
  393. fputs($s, "226 Transfer complete.\r\n");
  394. continue;
  395. }
  396. if(empty($pasv)) {
  397. fputs($s, "150 File status okay; about to open data connection\r\n");
  398. if(!$fs = stream_socket_client("tcp://$host:$port")) {
  399. fputs($s, "425 Can't open data connection\r\n");
  400. continue;
  401. }
  402. } else {
  403. fputs($s, "125 Data connection already open; transfer starting.\r\n");
  404. $fs = $pasvs;
  405. }
  406. if((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, TRUE, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) {
  407. die("SSLv23 handshake failed.\n");
  408. }
  409. if(empty($m[1]) || $m[1] !== 'emptydir') {
  410. fputs($fs, "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .\r\n");
  411. fputs($fs, "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ..\r\n");
  412. fputs($fs, "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar\r\n");
  413. fputs($fs, "fact=val=ue;empty=; path;name\r\n");
  414. fputs($fs, "no_space\r\n");
  415. fputs($fs, "no_semi pathname\r\n");
  416. fputs($fs, "no_eq; pathname\r\n");
  417. }
  418. fputs($s, "226 Closing data Connection.\r\n");
  419. fclose($fs);
  420. }elseif (preg_match('/^SIZE \/bug73457/', $buf)) {
  421. fputs($s, "213 10\r\n");
  422. }elseif (preg_match("/^SITE/", $buf)) {
  423. fputs($s, "500 Syntax error, command unrecognized.\r\n");
  424. }else {
  425. dump_and_exit($buf);
  426. }
  427. }
  428. exit;
  429. }
  430. fclose($socket);
  431. ?>