1/* 2 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.net.*; 25import java.io.*; 26import java.util.regex.*; 27import java.security.*; 28import javax.net.ssl.*; 29 30/* 31 * This class handles one client connection. It will interpret and act on the 32 * commands (like USER, GET, PUT etc...) sent through the socket passed to 33 * the constructor. 34 * 35 * To function it needs to be provided 2 handlers, one for the filesystem 36 * and one for authentication. 37 * @see FileSystemHandler 38 * @see AuthHandler 39 * @see #setHandlers(FtpFileSystemHandler,FtpAuthHandler) 40 */ 41 42public class FtpCommandHandler extends Thread { 43 private FtpServer parent = null; 44 private Socket cmd = null; 45 private Socket oldCmd = null; 46 private InetAddress clientAddr = null; 47 private ServerSocket pasv = null; 48 49 private BufferedReader in = null; 50 51 private PrintStream out = null; 52 53 private FtpFileSystemHandler fsh = null; 54 private FtpAuthHandler auth = null; 55 56 private boolean done = false; 57 58 private String username = null; 59 private String password = null; 60 private String account = null; 61 private boolean logged = false; 62 private boolean epsvAll = false; 63 private int dataPort = 0; 64 private InetAddress dataAddress = null; 65 private boolean pasvEnabled = true; 66 private boolean portEnabled = true; 67 private boolean extendedEnabled = true; 68 private boolean binary = true; 69 private String renameFrom = null; 70 private long restart = 0; 71 private boolean useCrypto = false; 72 private boolean useDataCrypto = false; 73 private SSLSocketFactory sslFact = null; 74 75 private final int ERROR = -1; 76 private final int QUIT = 0; 77 private final int USER = 1; 78 private final int PASS = 2; 79 private final int CWD = 3; 80 private final int CDUP = 4; 81 private final int PWD = 5; 82 private final int TYPE = 6; 83 private final int NOOP = 7; 84 private final int RETR = 8; 85 private final int PORT = 9; 86 private final int PASV = 10; 87 private final int EPSV = 11; 88 private final int EPRT = 12; 89 private final int SYST = 13; 90 private final int STOR = 14; 91 private final int STOU = 15; 92 private final int LIST = 16; 93 private final int NLST = 17; 94 private final int RNFR = 18; 95 private final int RNTO = 19; 96 private final int DELE = 20; 97 private final int REST = 21; 98 private final int AUTH = 22; 99 private final int FEAT = 23; 100 private final int CCC = 24; 101 private final int PROT = 25; 102 private final int PBSZ = 26; 103 104 private String[] commands = 105 { "QUIT", "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE", "NOOP", "RETR", 106 "PORT", "PASV", "EPSV", "EPRT", "SYST", "STOR", "STOU", "LIST", "NLST", 107 "RNFR", "RNTO", "DELE", "REST", "AUTH", "FEAT", "CCC", "PROT", "PBSZ" 108 }; 109 110 private boolean isPasvSet() { 111 if (pasv != null && !pasvEnabled) { 112 try { 113 pasv.close(); 114 } catch ( IOException e) { 115 116 } 117 pasv = null; 118 } 119 if (pasvEnabled && pasv != null) 120 return true; 121 return false; 122 } 123 124 private OutputStream getOutDataStream() throws IOException { 125 if (isPasvSet()) { 126 Socket s = pasv.accept(); 127 if (useCrypto && useDataCrypto) { 128 SSLSocket ssl = (SSLSocket) sslFact.createSocket(s, clientAddr.getHostName(), s.getPort(), true); 129 ssl.setUseClientMode(false); 130 s = ssl; 131 } 132 return s.getOutputStream(); 133 } 134 if (dataAddress != null) { 135 Socket s; 136 if (useCrypto) { 137 s = sslFact.createSocket(dataAddress, dataPort); 138 } else 139 s = new Socket(dataAddress, dataPort); 140 dataAddress = null; 141 dataPort = 0; 142 return s.getOutputStream(); 143 } 144 return null; 145 } 146 147 private InputStream getInDataStream() throws IOException { 148 if (isPasvSet()) { 149 Socket s = pasv.accept(); 150 if (useCrypto && useDataCrypto) { 151 SSLSocket ssl = (SSLSocket) sslFact.createSocket(s, clientAddr.getHostName(), s.getPort(), true); 152 ssl.setUseClientMode(false); 153 s = ssl; 154 } 155 return s.getInputStream(); 156 } 157 if (dataAddress != null) { 158 Socket s; 159 if (useCrypto) { 160 s = sslFact.createSocket(dataAddress, dataPort); 161 } else 162 s = new Socket(dataAddress, dataPort); 163 dataAddress = null; 164 dataPort = 0; 165 return s.getInputStream(); 166 } 167 return null; 168 } 169 170 private void parsePort(String port_arg) throws IOException { 171 if (epsvAll) { 172 out.println("501 PORT not allowed after EPSV ALL."); 173 return; 174 } 175 if (!portEnabled) { 176 out.println("500 PORT command is disabled, please use PASV."); 177 return; 178 } 179 StringBuffer host; 180 int i = 0, j = 4; 181 while (j > 0) { 182 i = port_arg.indexOf(',', i + 1); 183 if (i < 0) 184 break; 185 j--; 186 } 187 if (j != 0) { 188 out.println("500 '" + port_arg + "': command not understood."); 189 return; 190 } 191 try { 192 host = new StringBuffer(port_arg.substring(0, i)); 193 for (j = 0; j < host.length(); j++) 194 if (host.charAt(j) == ',') 195 host.setCharAt(j, '.'); 196 String ports = port_arg.substring(i + 1); 197 i = ports.indexOf(','); 198 dataPort = Integer.parseInt(ports.substring(0, i)) << 8; 199 dataPort += (Integer.parseInt(ports.substring(i + 1))); 200 dataAddress = InetAddress.getByName(host.toString()); 201 out.println("200 Command okay."); 202 } catch (Exception ex3) { 203 dataPort = 0; 204 dataAddress = null; 205 out.println("500 '" + port_arg + "': command not understood."); 206 } 207 } 208 209 private void parseEprt(String arg) { 210 if (epsvAll) { 211 out.println("501 PORT not allowed after EPSV ALL"); 212 return; 213 } 214 if (!extendedEnabled || !portEnabled) { 215 out.println("500 EPRT is disabled, use PASV instead"); 216 return; 217 } 218 Pattern p = Pattern.compile("\\|(\\d)\\|(.*)\\|(\\d+)\\|"); 219 Matcher m = p.matcher(arg); 220 if (!m.find()) { 221 out.println("500 '" + arg + "': command not understood."); 222 return; 223 } 224 try { 225 dataAddress = InetAddress.getByName(m.group(2)); 226 } catch (UnknownHostException e) { 227 out.println("500 " + arg + ": invalid address."); 228 dataAddress = null; 229 return; 230 } 231 dataPort = Integer.parseInt(m.group(3)); 232 out.println("200 Command okay."); 233 } 234 235 private void doPasv() { 236 if (!pasvEnabled) { 237 out.println("500 PASV is disabled, use PORT."); 238 return; 239 } 240 try { 241 if (pasv == null) 242 pasv = new ServerSocket(0); 243 int port = pasv.getLocalPort(); 244 InetAddress rAddress = cmd.getLocalAddress(); 245 if (rAddress instanceof Inet6Address) { 246 out.println("500 PASV illegal over IPv6 addresses, use EPSV."); 247 return; 248 } 249 byte[] a = rAddress.getAddress(); 250 out.println("227 Entering Passive Mode " + a[0] + "," + a[1] + "," + a[2] + "," + a[3] + "," + 251 (port >> 8) + "," + (port & 0xff) ); 252 } catch (IOException e) { 253 out.println("425 can't build data connection: Connection refused."); 254 } 255 } 256 257 private void doEpsv(String arg) { 258 if (!extendedEnabled || !pasvEnabled) { 259 out.println("500 EPSV disabled, use PORT or PASV."); 260 return; 261 } 262 if ("all".equalsIgnoreCase(arg)) { 263 out.println("200 EPSV ALL Command successful."); 264 epsvAll = true; 265 return; 266 } 267 try { 268 if (pasv == null) 269 pasv = new ServerSocket(0); 270 int port = pasv.getLocalPort(); 271 out.println("229 Entering Extended Passive Mode (|||" + port + "|)"); 272 } catch (IOException e) { 273 out.println("500 Can't create data connection."); 274 } 275 } 276 277 private void doRetr(String arg) { 278 try { 279 OutputStream dOut = getOutDataStream(); 280 if (dOut != null) { 281 InputStream dIn = fsh.getFile(arg); 282 if (dIn == null) { 283 out.println("550 File not found."); 284 dOut.close(); 285 return; 286 } 287 out.println("150 Opening " + (binary ? "BINARY " : "ASCII ") + " data connection for file " + arg + 288 "(" + fsh.getFileSize(arg) + " bytes)."); 289 if (binary) { 290 byte[] buf = new byte[2048]; 291 dOut = new BufferedOutputStream(dOut); 292 int count; 293 if (restart > 0) { 294 dIn.skip(restart); 295 restart = 0; 296 } 297 do { 298 count = dIn.read(buf); 299 if (count > 0) 300 dOut.write(buf, 0, count); 301 } while (count >= 0); 302 dOut.close(); 303 dIn.close(); 304 out.println("226 Transfer complete."); 305 } 306 } 307 } catch (IOException e) { 308 309 } 310 } 311 312 private void doStor(String arg, boolean unique) { 313 try { 314 InputStream dIn = getInDataStream(); 315 if (dIn != null) { 316 OutputStream dOut = fsh.putFile(arg); 317 if (dOut == null) { 318 out.println("500 Can't create file " + arg); 319 dIn.close(); 320 return; 321 } 322 out.println("150 Opening " + (binary ? "BINARY " : "ASCII ") + " data connection for file " + arg); 323 if (binary) { 324 byte[] buf = new byte[2048]; 325 dOut = new BufferedOutputStream(dOut); 326 int count; 327 do { 328 count = dIn.read(buf); 329 if (count > 0) 330 dOut.write(buf, 0, count); 331 } while (count >= 0); 332 dOut.close(); 333 dIn.close(); 334 out.println("226 Transfer complete."); 335 } 336 } 337 } catch (IOException e) { 338 339 } 340 } 341 342 private void doList() { 343 try { 344 OutputStream dOut = getOutDataStream(); 345 if (dOut != null) { 346 InputStream dIn = fsh.listCurrentDir(); 347 if (dIn == null) { 348 out.println("550 File not found."); 349 dOut.close(); 350 return; 351 } 352 out.println("150 Opening ASCII data connection for file list"); 353 byte[] buf = new byte[2048]; 354 dOut = new BufferedOutputStream(dOut); 355 int count; 356 do { 357 count = dIn.read(buf); 358 if (count > 0) 359 dOut.write(buf, 0, count); 360 } while (count >= 0); 361 dOut.close(); 362 dIn.close(); 363 out.println("226 Transfer complete."); 364 } 365 } catch (IOException e) { 366 367 } 368 } 369 370 private boolean useTLS() { 371 if (sslFact == null) { 372 sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault(); 373 } 374 if (sslFact == null) 375 return false; 376 return true; 377 } 378 379 private void stopTLS() { 380 if (useCrypto) { 381 SSLSocket ssl = (SSLSocket) cmd; 382 try { 383 ssl.close(); 384 } catch (IOException e) { 385 // nada 386 } 387 cmd = oldCmd; 388 oldCmd = null; 389 try { 390 in = new BufferedReader(new InputStreamReader(cmd.getInputStream())); 391 out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1"); 392 } catch (Exception ex) { 393 394 } 395 } 396 } 397 398 public void setHandlers(FtpFileSystemHandler f, FtpAuthHandler a) { 399 fsh = f; 400 auth = a; 401 } 402 403 public FtpCommandHandler(Socket cl, FtpServer p) { 404 parent = p; 405 cmd = cl; 406 clientAddr = cl.getInetAddress(); 407 } 408 409 public void terminate() { 410 done = true; 411 } 412 413 private int parseCmd(StringBuffer cmd) { 414 415 if (cmd == null || cmd.length() < 3) // Shortest command is 3 char long 416 return ERROR; 417 int blank = cmd.indexOf(" "); 418 if (blank < 0) 419 blank = cmd.length(); 420 if (blank < 3) 421 return ERROR; 422 String s = cmd.substring(0,blank); 423 cmd.delete(0, blank + 1); 424 System.out.println("parse: cmd = " + s + " arg = " +cmd.toString()); 425 for (int i = 0; i < commands.length; i++) 426 if (s.equalsIgnoreCase(commands[i])) 427 return i; 428 // Unknown command 429 return ERROR; 430 } 431 432 private boolean checkLogged() { 433 if (!logged) { 434 out.println("530 Not logged in."); 435 return false; 436 } 437 return true; 438 } 439 440 public void run() { 441 try { 442 // cmd.setSoTimeout(2000); 443 in = new BufferedReader(new InputStreamReader(cmd.getInputStream())); 444 out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1"); 445 out.println("---------------------------------\n220 Java FTP test server" 446 + " (j2se 6.0) ready.\n \n Please send commands\n" 447 + "-----------------------------\n\n\n"); 448 out.flush(); 449 if (auth.authType() == 0) // No auth needed 450 logged = true; 451 } catch (IOException e) { 452 e.printStackTrace(); 453 return; 454 } 455 456 String str; 457 StringBuffer buf; 458 int res; 459 while (!done) { 460 try { 461 str = in.readLine(); 462 System.out.println("line: " + str); 463 buf = new StringBuffer(str); 464 res = parseCmd(buf); 465 switch (res) { 466 case ERROR: 467 out.println("500 '" + str +"': command not understood."); 468 break; 469 case QUIT: 470 out.println("221 Goodbye."); 471 done = true; 472 break; 473 case USER: 474 logged = false; 475 username = buf.toString(); 476 if (auth.authType() > 1) 477 out.println("331 User name okay, need password."); 478 else { 479 if (auth.authenticate(username, null)) { 480 out.println("230 User logged in, proceed."); 481 logged = true; 482 } else { 483 out.println("331 User name okay, need password."); 484 } 485 } 486 break; 487 case PASS: 488 if (logged || (username == null)) { 489 out.println("503 Login with USER first."); 490 break; 491 } 492 password = buf.toString(); 493 if (auth.authType() == 3) { 494 out.println("332 Need account for login."); 495 break; 496 } 497 if (auth.authenticate(username, password)) { 498 logged = true; 499 out.println("230 User " + username + " logged in."); 500 break; 501 } 502 out.println("530 Login incorrect."); 503 username = null; 504 break; 505 case CWD: 506 if (checkLogged()) { 507 String path = buf.toString(); 508 if (fsh.cd(path)) { 509 out.println("250 CWD command successful."); 510 } else { 511 out.println("550 " + path + ": no such file or directory."); 512 } 513 } 514 break; 515 case CDUP: 516 if (checkLogged()) { 517 if (fsh.cdUp()) 518 out.println("250 CWD command successful."); 519 else 520 out.println("550 invalid path."); 521 } 522 break; 523 case PWD: 524 if (checkLogged()) { 525 String s = fsh.pwd(); 526 out.println("257 \"" + s + "\" is current directory"); 527 } 528 break; 529 case NOOP: 530 if (checkLogged()) { 531 out.println("200 NOOP command successful."); 532 } 533 break; 534 case PORT: 535 if (checkLogged()) { 536 parsePort(buf.toString()); 537 } 538 break; 539 case EPRT: 540 if (checkLogged()) { 541 parseEprt(buf.toString()); 542 } 543 break; 544 case PASV: 545 if (checkLogged()) 546 doPasv(); 547 break; 548 case EPSV: 549 if (checkLogged()) 550 doEpsv(buf.toString()); 551 break; 552 case RETR: 553 if (checkLogged()) { 554 doRetr(buf.toString()); 555 } 556 break; 557 case SYST: 558 if (checkLogged()) { 559 out.println("215 UNIX Type: L8 Version: Java 6.0"); 560 } 561 break; 562 case TYPE: 563 if (checkLogged()) { 564 String arg = buf.toString(); 565 if (arg.length() != 1 || "AIE".indexOf(arg.charAt(0)) < 0) { 566 out.println("500 'TYPE " + arg + "' command not understood."); 567 continue; 568 } 569 out.println("200 Type set to " + buf.toString() + "."); 570 if (arg.charAt(0) == 'I') 571 binary = true; 572 else 573 binary = false; 574 } 575 break; 576 case STOR: 577 case STOU: 578 // TODO: separate STOR and STOU (Store Unique) 579 if (checkLogged()) { 580 doStor(buf.toString(), false); 581 } 582 break; 583 case LIST: 584 if (checkLogged()) { 585 doList(); 586 } 587 break; 588 case NLST: 589 // TODO: implememt 590 break; 591 case DELE: 592 if (checkLogged()) { 593 String arg = buf.toString(); 594 if (fsh.removeFile(arg)) { 595 out.println("250 file " + arg + " deleted."); 596 break; 597 } 598 out.println("550 " + arg + ": no such file or directory."); 599 } 600 break; 601 case RNFR: 602 if (checkLogged()) { 603 if (renameFrom != null) { 604 out.println("503 Bad sequence of commands."); 605 break; 606 } 607 renameFrom = buf.toString(); 608 if (fsh.fileExists(renameFrom)) { 609 out.println("350 File or directory exists, ready for destination name."); 610 } else { 611 out.println("550 " + renameFrom + ": no such file or directory"); 612 renameFrom = null; 613 } 614 } 615 break; 616 case RNTO: 617 if (checkLogged()) { 618 if (renameFrom == null) { 619 out.println("503 Bad sequence of commands."); 620 break; 621 } 622 if (fsh.rename(renameFrom, buf.toString())) { 623 out.println("250 Rename successful"); 624 } else { 625 out.println("550 Rename "); 626 } 627 renameFrom = null; 628 } 629 break; 630 case REST: 631 if (checkLogged()) { 632 String arg = buf.toString(); 633 restart = Long.parseLong(arg); 634 if (restart > 0) 635 out.println("350 Restarting at " + restart + ". Send STORE or RETRIEVE to initiate transfer"); 636 else 637 out.println("501 Syntax error in command of arguments."); 638 } 639 break; 640 case FEAT: 641 out.println("211-Features:"); 642 out.println(" REST STREAM"); 643 out.println(" PBSZ"); 644 out.println(" AUTH TLS"); 645 out.println(" PROT P"); 646 out.println(" CCC"); 647 out.println("211 End"); 648 break; 649 case AUTH: 650 if ("TLS".equalsIgnoreCase(buf.toString()) && useTLS()) { 651 out.println("234 TLS Authentication OK."); 652 out.flush(); 653 SSLSocket ssl; 654 String[] suites = sslFact.getSupportedCipherSuites(); 655 try { 656 ssl = (SSLSocket) sslFact.createSocket(cmd, cmd.getInetAddress().getHostName(), cmd.getPort(), false); 657 ssl.setUseClientMode(false); 658 ssl.setEnabledCipherSuites(suites); 659 ssl.startHandshake(); 660 } catch (IOException ioe) { 661 ioe.printStackTrace(); 662 out.println("550 Unable to create secure channel."); 663 break; 664 } 665 oldCmd = cmd; 666 cmd = ssl; 667 out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1"); 668 in = new BufferedReader(new InputStreamReader(cmd.getInputStream())); 669 System.out.println("Secure socket created!"); 670 useCrypto = true; 671 break; 672 } 673 out.println("501 Unknown or unsupported AUTH type"); 674 break; 675 case CCC: 676 out.println("200 Command OK."); 677 stopTLS(); 678 break; 679 case PROT: 680 String arg = buf.toString(); 681 if ("C".equalsIgnoreCase(arg)) { 682 // PROT C : Clear protection level 683 // No protection on data channel; 684 useDataCrypto = false; 685 out.println("200 Command OK."); 686 break; 687 } 688 if ("P".equalsIgnoreCase(arg)) { 689 // PROT P : Private protection level 690 // Data channel is integrity and confidentiality protected 691 useDataCrypto = true; 692 out.println("200 Command OK."); 693 break; 694 } 695 out.println("537 Requested PROT level not supported by security mechanism."); 696 break; 697 case PBSZ: 698 // TODO: finish 699 out.println("200 Command OK."); 700 break; 701 702 } 703 704 } catch (InterruptedIOException ie) { 705 // loop 706 } catch (IOException e) { 707 e.printStackTrace(); 708 return; 709 } 710 } 711 try { 712 in.close(); 713 out.close(); 714 cmd.close(); 715 } catch (IOException e) { 716 } 717 parent.removeClient(this); 718 } 719} 720