jdk/test/sun/net/www/ftptest/FtpCommandHandler.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /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);
+    }
+}