--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/net/www/ftptest/FtpCommandHandler.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,717 @@
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.util.regex.*;
+import java.security.*;
+import javax.net.ssl.*;
+
+/*
+ * This class handles one client connection. It will interpret and act on the
+ * commands (like USER, GET, PUT etc...) sent through the socket passed to
+ * the constructor.
+ *
+ * To function it needs to be provided 2 handlers, one for the filesystem
+ * and one for authentication.
+ * @see FileSystemHandler
+ * @see AuthHandler
+ * @see #setHandlers(FtpFileSystemHandler,FtpAuthHandler)
+ */
+
+public class FtpCommandHandler extends Thread {
+ private FtpServer parent = null;
+ private Socket cmd = null;
+ private Socket oldCmd = null;
+ private InetAddress clientAddr = null;
+ private ServerSocket pasv = null;
+
+ private BufferedReader in = null;
+
+ private PrintStream out = null;
+
+ private FtpFileSystemHandler fsh = null;
+ private FtpAuthHandler auth = null;
+
+ private boolean done = false;
+
+ private String username = null;
+ private String password = null;
+ private String account = null;
+ private boolean logged = false;
+ private boolean epsvAll = false;
+ private int dataPort = 0;
+ private InetAddress dataAddress = null;
+ private boolean pasvEnabled = true;
+ private boolean portEnabled = true;
+ private boolean extendedEnabled = true;
+ private boolean binary = true;
+ private String renameFrom = null;
+ private long restart = 0;
+ private boolean useCrypto = false;
+ private boolean useDataCrypto = false;
+ private SSLSocketFactory sslFact = null;
+
+ private final int ERROR = -1;
+ private final int QUIT = 0;
+ private final int USER = 1;
+ private final int PASS = 2;
+ private final int CWD = 3;
+ private final int CDUP = 4;
+ private final int PWD = 5;
+ private final int TYPE = 6;
+ private final int NOOP = 7;
+ private final int RETR = 8;
+ private final int PORT = 9;
+ private final int PASV = 10;
+ private final int EPSV = 11;
+ private final int EPRT = 12;
+ private final int SYST = 13;
+ private final int STOR = 14;
+ private final int STOU = 15;
+ private final int LIST = 16;
+ private final int NLST = 17;
+ private final int RNFR = 18;
+ private final int RNTO = 19;
+ private final int DELE = 20;
+ private final int REST = 21;
+ private final int AUTH = 22;
+ private final int FEAT = 23;
+ private final int CCC = 24;
+ private final int PROT = 25;
+ private final int PBSZ = 26;
+
+ private String[] commands =
+ { "QUIT", "USER", "PASS", "CWD", "CDUP", "PWD", "TYPE", "NOOP", "RETR",
+ "PORT", "PASV", "EPSV", "EPRT", "SYST", "STOR", "STOU", "LIST", "NLST",
+ "RNFR", "RNTO", "DELE", "REST", "AUTH", "FEAT", "CCC", "PROT", "PBSZ"
+ };
+
+ private boolean isPasvSet() {
+ if (pasv != null && !pasvEnabled) {
+ try {
+ pasv.close();
+ } catch ( IOException e) {
+
+ }
+ pasv = null;
+ }
+ if (pasvEnabled && pasv != null)
+ return true;
+ return false;
+ }
+
+ private OutputStream getOutDataStream() throws IOException {
+ if (isPasvSet()) {
+ Socket s = pasv.accept();
+ if (useCrypto && useDataCrypto) {
+ SSLSocket ssl = (SSLSocket) sslFact.createSocket(s, clientAddr.getHostName(), s.getPort(), true);
+ ssl.setUseClientMode(false);
+ s = ssl;
+ }
+ return s.getOutputStream();
+ }
+ if (dataAddress != null) {
+ Socket s;
+ if (useCrypto) {
+ s = sslFact.createSocket(dataAddress, dataPort);
+ } else
+ s = new Socket(dataAddress, dataPort);
+ dataAddress = null;
+ dataPort = 0;
+ return s.getOutputStream();
+ }
+ return null;
+ }
+
+ private InputStream getInDataStream() throws IOException {
+ if (isPasvSet()) {
+ Socket s = pasv.accept();
+ if (useCrypto && useDataCrypto) {
+ SSLSocket ssl = (SSLSocket) sslFact.createSocket(s, clientAddr.getHostName(), s.getPort(), true);
+ ssl.setUseClientMode(false);
+ s = ssl;
+ }
+ return s.getInputStream();
+ }
+ if (dataAddress != null) {
+ Socket s;
+ if (useCrypto) {
+ s = sslFact.createSocket(dataAddress, dataPort);
+ } else
+ s = new Socket(dataAddress, dataPort);
+ dataAddress = null;
+ dataPort = 0;
+ return s.getInputStream();
+ }
+ return null;
+ }
+
+ private void parsePort(String port_arg) throws IOException {
+ if (epsvAll) {
+ out.println("501 PORT not allowed after EPSV ALL.");
+ return;
+ }
+ if (!portEnabled) {
+ out.println("500 PORT command is disabled, please use PASV.");
+ return;
+ }
+ StringBuffer host;
+ int i = 0, j = 4;
+ while (j > 0) {
+ i = port_arg.indexOf(',', i + 1);
+ if (i < 0)
+ break;
+ j--;
+ }
+ if (j != 0) {
+ out.println("500 '" + port_arg + "': command not understood.");
+ return;
+ }
+ try {
+ host = new StringBuffer(port_arg.substring(0, i));
+ for (j = 0; j < host.length(); j++)
+ if (host.charAt(j) == ',')
+ host.setCharAt(j, '.');
+ String ports = port_arg.substring(i + 1);
+ i = ports.indexOf(',');
+ dataPort = Integer.parseInt(ports.substring(0, i)) << 8;
+ dataPort += (Integer.parseInt(ports.substring(i + 1)));
+ dataAddress = InetAddress.getByName(host.toString());
+ out.println("200 Command okay.");
+ } catch (Exception ex3) {
+ dataPort = 0;
+ dataAddress = null;
+ out.println("500 '" + port_arg + "': command not understood.");
+ }
+ }
+
+ private void parseEprt(String arg) {
+ if (epsvAll) {
+ out.println("501 PORT not allowed after EPSV ALL");
+ return;
+ }
+ if (!extendedEnabled || !portEnabled) {
+ out.println("500 EPRT is disabled, use PASV instead");
+ return;
+ }
+ Pattern p = Pattern.compile("\\|(\\d)\\|(.*)\\|(\\d+)\\|");
+ Matcher m = p.matcher(arg);
+ if (!m.find()) {
+ out.println("500 '" + arg + "': command not understood.");
+ return;
+ }
+ try {
+ dataAddress = InetAddress.getByName(m.group(2));
+ } catch (UnknownHostException e) {
+ out.println("500 " + arg + ": invalid address.");
+ dataAddress = null;
+ return;
+ }
+ dataPort = Integer.parseInt(m.group(3));
+ out.println("200 Command okay.");
+ }
+
+ private void doPasv() {
+ if (!pasvEnabled) {
+ out.println("500 PASV is disabled, use PORT.");
+ return;
+ }
+ try {
+ if (pasv == null)
+ pasv = new ServerSocket(0);
+ int port = pasv.getLocalPort();
+ InetAddress rAddress = cmd.getLocalAddress();
+ if (rAddress instanceof Inet6Address) {
+ out.println("500 PASV illegal over IPv6 addresses, use EPSV.");
+ return;
+ }
+ byte[] a = rAddress.getAddress();
+ out.println("227 Entering Passive Mode " + a[0] + "," + a[1] + "," + a[2] + "," + a[3] + "," +
+ (port >> 8) + "," + (port & 0xff) );
+ } catch (IOException e) {
+ out.println("425 can't build data connection: Connection refused.");
+ }
+ }
+
+ private void doEpsv(String arg) {
+ if (!extendedEnabled || !pasvEnabled) {
+ out.println("500 EPSV disabled, use PORT or PASV.");
+ return;
+ }
+ if ("all".equalsIgnoreCase(arg)) {
+ out.println("200 EPSV ALL Command successful.");
+ epsvAll = true;
+ return;
+ }
+ try {
+ if (pasv == null)
+ pasv = new ServerSocket(0);
+ int port = pasv.getLocalPort();
+ out.println("229 Entering Extended Passive Mode (|||" + port + "|)");
+ } catch (IOException e) {
+ out.println("500 Can't create data connection.");
+ }
+ }
+
+ private void doRetr(String arg) {
+ try {
+ OutputStream dOut = getOutDataStream();
+ if (dOut != null) {
+ InputStream dIn = fsh.getFile(arg);
+ if (dIn == null) {
+ out.println("550 File not found.");
+ dOut.close();
+ return;
+ }
+ out.println("150 Opening " + (binary ? "BINARY " : "ASCII ") + " data connection for file " + arg +
+ "(" + fsh.getFileSize(arg) + " bytes).");
+ if (binary) {
+ byte[] buf = new byte[2048];
+ dOut = new BufferedOutputStream(dOut);
+ int count;
+ if (restart > 0) {
+ dIn.skip(restart);
+ restart = 0;
+ }
+ do {
+ count = dIn.read(buf);
+ if (count > 0)
+ dOut.write(buf, 0, count);
+ } while (count >= 0);
+ dOut.close();
+ dIn.close();
+ out.println("226 Transfer complete.");
+ }
+ }
+ } catch (IOException e) {
+
+ }
+ }
+
+ private void doStor(String arg, boolean unique) {
+ try {
+ InputStream dIn = getInDataStream();
+ if (dIn != null) {
+ OutputStream dOut = fsh.putFile(arg);
+ if (dOut == null) {
+ out.println("500 Can't create file " + arg);
+ dIn.close();
+ return;
+ }
+ out.println("150 Opening " + (binary ? "BINARY " : "ASCII ") + " data connection for file " + arg);
+ if (binary) {
+ byte[] buf = new byte[2048];
+ dOut = new BufferedOutputStream(dOut);
+ int count;
+ do {
+ count = dIn.read(buf);
+ if (count > 0)
+ dOut.write(buf, 0, count);
+ } while (count >= 0);
+ dOut.close();
+ dIn.close();
+ out.println("226 Transfer complete.");
+ }
+ }
+ } catch (IOException e) {
+
+ }
+ }
+
+ private void doList() {
+ try {
+ OutputStream dOut = getOutDataStream();
+ if (dOut != null) {
+ InputStream dIn = fsh.listCurrentDir();
+ if (dIn == null) {
+ out.println("550 File not found.");
+ dOut.close();
+ return;
+ }
+ out.println("150 Opening ASCII data connection for file list");
+ byte[] buf = new byte[2048];
+ dOut = new BufferedOutputStream(dOut);
+ int count;
+ do {
+ count = dIn.read(buf);
+ if (count > 0)
+ dOut.write(buf, 0, count);
+ } while (count >= 0);
+ dOut.close();
+ dIn.close();
+ out.println("226 Transfer complete.");
+ }
+ } catch (IOException e) {
+
+ }
+ }
+
+ private boolean useTLS() {
+ if (sslFact == null) {
+ sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ }
+ if (sslFact == null)
+ return false;
+ return true;
+ }
+
+ private void stopTLS() {
+ if (useCrypto) {
+ SSLSocket ssl = (SSLSocket) cmd;
+ try {
+ ssl.close();
+ } catch (IOException e) {
+ // nada
+ }
+ cmd = oldCmd;
+ oldCmd = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
+ out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1");
+ } catch (Exception ex) {
+
+ }
+ }
+ }
+
+ public void setHandlers(FtpFileSystemHandler f, FtpAuthHandler a) {
+ fsh = f;
+ auth = a;
+ }
+
+ public FtpCommandHandler(Socket cl, FtpServer p) {
+ parent = p;
+ cmd = cl;
+ clientAddr = cl.getInetAddress();
+ }
+
+ public void terminate() {
+ done = true;
+ }
+
+ private int parseCmd(StringBuffer cmd) {
+
+ if (cmd == null || cmd.length() < 3) // Shortest command is 3 char long
+ return ERROR;
+ int blank = cmd.indexOf(" ");
+ if (blank < 0)
+ blank = cmd.length();
+ if (blank < 3)
+ return ERROR;
+ String s = cmd.substring(0,blank);
+ cmd.delete(0, blank + 1);
+ System.out.println("parse: cmd = " + s + " arg = " +cmd.toString());
+ for (int i = 0; i < commands.length; i++)
+ if (s.equalsIgnoreCase(commands[i]))
+ return i;
+ // Unknown command
+ return ERROR;
+ }
+
+ private boolean checkLogged() {
+ if (!logged) {
+ out.println("530 Not logged in.");
+ return false;
+ }
+ return true;
+ }
+
+ public void run() {
+ try {
+ // cmd.setSoTimeout(2000);
+ in = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
+ out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1");
+ out.println("220 Java FTP test server (j2se 6.0) ready.");
+ out.flush();
+ if (auth.authType() == 0) // No auth needed
+ logged = true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ String str;
+ StringBuffer buf;
+ int res;
+ while (!done) {
+ try {
+ str = in.readLine();
+ System.out.println("line: " + str);
+ buf = new StringBuffer(str);
+ res = parseCmd(buf);
+ switch (res) {
+ case ERROR:
+ out.println("500 '" + str +"': command not understood.");
+ break;
+ case QUIT:
+ out.println("221 Goodbye.");
+ done = true;
+ break;
+ case USER:
+ logged = false;
+ username = buf.toString();
+ if (auth.authType() > 1)
+ out.println("331 User name okay, need password.");
+ else {
+ if (auth.authenticate(username, null)) {
+ out.println("230 User logged in, proceed.");
+ logged = true;
+ } else {
+ out.println("331 User name okay, need password.");
+ }
+ }
+ break;
+ case PASS:
+ if (logged || (username == null)) {
+ out.println("503 Login with USER first.");
+ break;
+ }
+ password = buf.toString();
+ if (auth.authType() == 3) {
+ out.println("332 Need account for login.");
+ break;
+ }
+ if (auth.authenticate(username, password)) {
+ logged = true;
+ out.println("230 User " + username + " logged in.");
+ break;
+ }
+ out.println("530 Login incorrect.");
+ username = null;
+ break;
+ case CWD:
+ if (checkLogged()) {
+ String path = buf.toString();
+ if (fsh.cd(path)) {
+ out.println("250 CWD command successful.");
+ } else {
+ out.println("550 " + path + ": no such file or directory.");
+ }
+ }
+ break;
+ case CDUP:
+ if (checkLogged()) {
+ if (fsh.cdUp())
+ out.println("250 CWD command successful.");
+ else
+ out.println("550 invalid path.");
+ }
+ break;
+ case PWD:
+ if (checkLogged()) {
+ String s = fsh.pwd();
+ out.println("257 \"" + s + "\" is current directory");
+ }
+ break;
+ case NOOP:
+ if (checkLogged()) {
+ out.println("200 NOOP command successful.");
+ }
+ break;
+ case PORT:
+ if (checkLogged()) {
+ parsePort(buf.toString());
+ }
+ break;
+ case EPRT:
+ if (checkLogged()) {
+ parseEprt(buf.toString());
+ }
+ break;
+ case PASV:
+ if (checkLogged())
+ doPasv();
+ break;
+ case EPSV:
+ if (checkLogged())
+ doEpsv(buf.toString());
+ break;
+ case RETR:
+ if (checkLogged()) {
+ doRetr(buf.toString());
+ }
+ break;
+ case SYST:
+ if (checkLogged()) {
+ out.println("215 UNIX Type: L8 Version: Java 6.0");
+ }
+ break;
+ case TYPE:
+ if (checkLogged()) {
+ String arg = buf.toString();
+ if (arg.length() != 1 || "AIE".indexOf(arg.charAt(0)) < 0) {
+ out.println("500 'TYPE " + arg + "' command not understood.");
+ continue;
+ }
+ out.println("200 Type set to " + buf.toString() + ".");
+ if (arg.charAt(0) == 'I')
+ binary = true;
+ else
+ binary = false;
+ }
+ break;
+ case STOR:
+ case STOU:
+ // TODO: separate STOR and STOU (Store Unique)
+ if (checkLogged()) {
+ doStor(buf.toString(), false);
+ }
+ break;
+ case LIST:
+ if (checkLogged()) {
+ doList();
+ }
+ break;
+ case NLST:
+ // TODO: implememt
+ break;
+ case DELE:
+ if (checkLogged()) {
+ String arg = buf.toString();
+ if (fsh.removeFile(arg)) {
+ out.println("250 file " + arg + " deleted.");
+ break;
+ }
+ out.println("550 " + arg + ": no such file or directory.");
+ }
+ break;
+ case RNFR:
+ if (checkLogged()) {
+ if (renameFrom != null) {
+ out.println("503 Bad sequence of commands.");
+ break;
+ }
+ renameFrom = buf.toString();
+ if (fsh.fileExists(renameFrom)) {
+ out.println("350 File or directory exists, ready for destination name.");
+ } else {
+ out.println("550 " + renameFrom + ": no such file or directory");
+ renameFrom = null;
+ }
+ }
+ break;
+ case RNTO:
+ if (checkLogged()) {
+ if (renameFrom == null) {
+ out.println("503 Bad sequence of commands.");
+ break;
+ }
+ if (fsh.rename(renameFrom, buf.toString())) {
+ out.println("250 Rename successful");
+ } else {
+ out.println("550 Rename ");
+ }
+ renameFrom = null;
+ }
+ break;
+ case REST:
+ if (checkLogged()) {
+ String arg = buf.toString();
+ restart = Long.parseLong(arg);
+ if (restart > 0)
+ out.println("350 Restarting at " + restart + ". Send STORE or RETRIEVE to initiate transfer");
+ else
+ out.println("501 Syntax error in command of arguments.");
+ }
+ break;
+ case FEAT:
+ out.println("211-Features:");
+ out.println(" REST STREAM");
+ out.println(" PBSZ");
+ out.println(" AUTH TLS");
+ out.println(" PROT P");
+ out.println(" CCC");
+ out.println("211 End");
+ break;
+ case AUTH:
+ if ("TLS".equalsIgnoreCase(buf.toString()) && useTLS()) {
+ out.println("234 TLS Authentication OK.");
+ out.flush();
+ SSLSocket ssl;
+ String[] suites = sslFact.getSupportedCipherSuites();
+ try {
+ ssl = (SSLSocket) sslFact.createSocket(cmd, cmd.getInetAddress().getHostName(), cmd.getPort(), false);
+ ssl.setUseClientMode(false);
+ ssl.setEnabledCipherSuites(suites);
+ ssl.startHandshake();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ out.println("550 Unable to create secure channel.");
+ break;
+ }
+ oldCmd = cmd;
+ cmd = ssl;
+ out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1");
+ in = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
+ System.out.println("Secure socket created!");
+ useCrypto = true;
+ break;
+ }
+ out.println("501 Unknown or unsupported AUTH type");
+ break;
+ case CCC:
+ out.println("200 Command OK.");
+ stopTLS();
+ break;
+ case PROT:
+ String arg = buf.toString();
+ if ("C".equalsIgnoreCase(arg)) {
+ // PROT C : Clear protection level
+ // No protection on data channel;
+ useDataCrypto = false;
+ out.println("200 Command OK.");
+ break;
+ }
+ if ("P".equalsIgnoreCase(arg)) {
+ // PROT P : Private protection level
+ // Data channel is integrity and confidentiality protected
+ useDataCrypto = true;
+ out.println("200 Command OK.");
+ break;
+ }
+ out.println("537 Requested PROT level not supported by security mechanism.");
+ break;
+ case PBSZ:
+ // TODO: finish
+ out.println("200 Command OK.");
+ break;
+
+ }
+
+ } catch (InterruptedIOException ie) {
+ // loop
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ try {
+ in.close();
+ out.close();
+ cmd.close();
+ } catch (IOException e) {
+ }
+ parent.removeClient(this);
+ }
+}