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