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