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