blob: 1626b198b394ba41b7ba82b33738f28f2ade6ad4 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2006 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any 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("220 Java FTP test server (j2se 6.0) ready.");
446 out.flush();
447 if (auth.authType() == 0) // No auth needed
448 logged = true;
449 } catch (IOException e) {
450 e.printStackTrace();
451 return;
452 }
453
454 String str;
455 StringBuffer buf;
456 int res;
457 while (!done) {
458 try {
459 str = in.readLine();
460 System.out.println("line: " + str);
461 buf = new StringBuffer(str);
462 res = parseCmd(buf);
463 switch (res) {
464 case ERROR:
465 out.println("500 '" + str +"': command not understood.");
466 break;
467 case QUIT:
468 out.println("221 Goodbye.");
469 done = true;
470 break;
471 case USER:
472 logged = false;
473 username = buf.toString();
474 if (auth.authType() > 1)
475 out.println("331 User name okay, need password.");
476 else {
477 if (auth.authenticate(username, null)) {
478 out.println("230 User logged in, proceed.");
479 logged = true;
480 } else {
481 out.println("331 User name okay, need password.");
482 }
483 }
484 break;
485 case PASS:
486 if (logged || (username == null)) {
487 out.println("503 Login with USER first.");
488 break;
489 }
490 password = buf.toString();
491 if (auth.authType() == 3) {
492 out.println("332 Need account for login.");
493 break;
494 }
495 if (auth.authenticate(username, password)) {
496 logged = true;
497 out.println("230 User " + username + " logged in.");
498 break;
499 }
500 out.println("530 Login incorrect.");
501 username = null;
502 break;
503 case CWD:
504 if (checkLogged()) {
505 String path = buf.toString();
506 if (fsh.cd(path)) {
507 out.println("250 CWD command successful.");
508 } else {
509 out.println("550 " + path + ": no such file or directory.");
510 }
511 }
512 break;
513 case CDUP:
514 if (checkLogged()) {
515 if (fsh.cdUp())
516 out.println("250 CWD command successful.");
517 else
518 out.println("550 invalid path.");
519 }
520 break;
521 case PWD:
522 if (checkLogged()) {
523 String s = fsh.pwd();
524 out.println("257 \"" + s + "\" is current directory");
525 }
526 break;
527 case NOOP:
528 if (checkLogged()) {
529 out.println("200 NOOP command successful.");
530 }
531 break;
532 case PORT:
533 if (checkLogged()) {
534 parsePort(buf.toString());
535 }
536 break;
537 case EPRT:
538 if (checkLogged()) {
539 parseEprt(buf.toString());
540 }
541 break;
542 case PASV:
543 if (checkLogged())
544 doPasv();
545 break;
546 case EPSV:
547 if (checkLogged())
548 doEpsv(buf.toString());
549 break;
550 case RETR:
551 if (checkLogged()) {
552 doRetr(buf.toString());
553 }
554 break;
555 case SYST:
556 if (checkLogged()) {
557 out.println("215 UNIX Type: L8 Version: Java 6.0");
558 }
559 break;
560 case TYPE:
561 if (checkLogged()) {
562 String arg = buf.toString();
563 if (arg.length() != 1 || "AIE".indexOf(arg.charAt(0)) < 0) {
564 out.println("500 'TYPE " + arg + "' command not understood.");
565 continue;
566 }
567 out.println("200 Type set to " + buf.toString() + ".");
568 if (arg.charAt(0) == 'I')
569 binary = true;
570 else
571 binary = false;
572 }
573 break;
574 case STOR:
575 case STOU:
576 // TODO: separate STOR and STOU (Store Unique)
577 if (checkLogged()) {
578 doStor(buf.toString(), false);
579 }
580 break;
581 case LIST:
582 if (checkLogged()) {
583 doList();
584 }
585 break;
586 case NLST:
587 // TODO: implememt
588 break;
589 case DELE:
590 if (checkLogged()) {
591 String arg = buf.toString();
592 if (fsh.removeFile(arg)) {
593 out.println("250 file " + arg + " deleted.");
594 break;
595 }
596 out.println("550 " + arg + ": no such file or directory.");
597 }
598 break;
599 case RNFR:
600 if (checkLogged()) {
601 if (renameFrom != null) {
602 out.println("503 Bad sequence of commands.");
603 break;
604 }
605 renameFrom = buf.toString();
606 if (fsh.fileExists(renameFrom)) {
607 out.println("350 File or directory exists, ready for destination name.");
608 } else {
609 out.println("550 " + renameFrom + ": no such file or directory");
610 renameFrom = null;
611 }
612 }
613 break;
614 case RNTO:
615 if (checkLogged()) {
616 if (renameFrom == null) {
617 out.println("503 Bad sequence of commands.");
618 break;
619 }
620 if (fsh.rename(renameFrom, buf.toString())) {
621 out.println("250 Rename successful");
622 } else {
623 out.println("550 Rename ");
624 }
625 renameFrom = null;
626 }
627 break;
628 case REST:
629 if (checkLogged()) {
630 String arg = buf.toString();
631 restart = Long.parseLong(arg);
632 if (restart > 0)
633 out.println("350 Restarting at " + restart + ". Send STORE or RETRIEVE to initiate transfer");
634 else
635 out.println("501 Syntax error in command of arguments.");
636 }
637 break;
638 case FEAT:
639 out.println("211-Features:");
640 out.println(" REST STREAM");
641 out.println(" PBSZ");
642 out.println(" AUTH TLS");
643 out.println(" PROT P");
644 out.println(" CCC");
645 out.println("211 End");
646 break;
647 case AUTH:
648 if ("TLS".equalsIgnoreCase(buf.toString()) && useTLS()) {
649 out.println("234 TLS Authentication OK.");
650 out.flush();
651 SSLSocket ssl;
652 String[] suites = sslFact.getSupportedCipherSuites();
653 try {
654 ssl = (SSLSocket) sslFact.createSocket(cmd, cmd.getInetAddress().getHostName(), cmd.getPort(), false);
655 ssl.setUseClientMode(false);
656 ssl.setEnabledCipherSuites(suites);
657 ssl.startHandshake();
658 } catch (IOException ioe) {
659 ioe.printStackTrace();
660 out.println("550 Unable to create secure channel.");
661 break;
662 }
663 oldCmd = cmd;
664 cmd = ssl;
665 out = new PrintStream(cmd.getOutputStream(), true, "ISO8859_1");
666 in = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
667 System.out.println("Secure socket created!");
668 useCrypto = true;
669 break;
670 }
671 out.println("501 Unknown or unsupported AUTH type");
672 break;
673 case CCC:
674 out.println("200 Command OK.");
675 stopTLS();
676 break;
677 case PROT:
678 String arg = buf.toString();
679 if ("C".equalsIgnoreCase(arg)) {
680 // PROT C : Clear protection level
681 // No protection on data channel;
682 useDataCrypto = false;
683 out.println("200 Command OK.");
684 break;
685 }
686 if ("P".equalsIgnoreCase(arg)) {
687 // PROT P : Private protection level
688 // Data channel is integrity and confidentiality protected
689 useDataCrypto = true;
690 out.println("200 Command OK.");
691 break;
692 }
693 out.println("537 Requested PROT level not supported by security mechanism.");
694 break;
695 case PBSZ:
696 // TODO: finish
697 out.println("200 Command OK.");
698 break;
699
700 }
701
702 } catch (InterruptedIOException ie) {
703 // loop
704 } catch (IOException e) {
705 e.printStackTrace();
706 return;
707 }
708 }
709 try {
710 in.close();
711 out.close();
712 cmd.close();
713 } catch (IOException e) {
714 }
715 parent.removeClient(this);
716 }
717}