Source code: jsd/ftp/server/ftp/FtpConnection.java
1 /*
2 * ----------------------------------------------------------------------------
3 * JStrangeDownloader and all accompanying source code files are
4 * Copyright (C) 2002 Dusty Davidson (dustyd@iastate.edu)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * ----------------------------------------------------------------------------
20 *
21 * Please see gpl.txt for the full text of the GNU General Public
22 * License.
23 */
24
25 package jsd.ftp.server.ftp;
26
27 import java.io.File;
28 import java.io.RandomAccessFile;
29 import java.io.InputStream;
30 import java.io.FileInputStream;
31 import java.io.OutputStream;
32 import java.io.FileOutputStream;
33 import java.io.Writer;
34 import java.io.OutputStreamWriter;
35 import java.io.IOException;
36 import java.net.Socket;
37 import java.net.InetAddress;
38 import java.net.UnknownHostException;
39 import java.text.SimpleDateFormat;
40 import java.util.Date;
41 import java.util.StringTokenizer;
42
43 import jsd.ftp.io.IoUtils;
44 import jsd.ftp.io.StreamConnector;
45
46 /**
47 * This class handles each ftp connection. Here all the ftp command
48 * methods take two arguments - a ftp request and a writer object.
49 * This is the main backbone of the ftp server.
50 * <br>
51 * The ftp command method signature is:
52 * <code>public void doXYZ(FtpRequest request, FtpWriter out) throws IOException</code>.
53 * <br>
54 * Here <code>XYZ</code> is the capitalized ftp command.
55 *
56 * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
57 */
58 public
59 class FtpConnection extends BaseFtpConnection {
60
61 private final static SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
62
63 // command state specific temporary variables
64 private boolean mbReset = false;
65 private long mlSkipLen = 0;
66
67 private boolean mbRenFr = false;
68 private String mstRenFr = null;
69
70 private boolean mbUser = false;
71 private boolean mbPass = false;
72
73 /**
74 * Set configuration file and the control socket.
75 */
76 public FtpConnection(FtpConfig cfg, Socket soc) {
77 super(cfg, soc);
78 }
79
80 /**
81 * Check the user permission to execute this command.
82 */
83 protected boolean hasPermission(FtpRequest request) {
84 String cmd = request.getCommand();
85 return mUser.hasLoggedIn() ||
86 cmd.equals("USER") ||
87 cmd.equals("PASS") ||
88 cmd.equals("HELP");
89 }
90
91 /**
92 * Reset temporary state variables.
93 */
94 private void resetState() {
95 mbRenFr = false;
96 mstRenFr = null;
97
98 mbReset = false;
99 mlSkipLen = 0;
100
101 mbUser = false;
102 mbPass = false;
103 }
104
105 ////////////////////////////////////////////////////////////
106 ///////////////// all the FTP handlers /////////////////
107 ////////////////////////////////////////////////////////////
108 /**
109 * <code>ABOR <CRLF></code><br>
110 *
111 * This command tells the server to abort the previous FTP
112 * service command and any associated transfer of data.
113 * No action is to be taken if the previous command
114 * has been completed (including data transfer). The control
115 * connection is not to be closed by the server, but the data
116 * connection must be closed.
117 * Current implementation does not do anything. As here data
118 * transfers are not multi-threaded.
119 */
120 public void doABOR(FtpRequest request, FtpWriter out) throws IOException {
121
122 // reset state variables
123 resetState();
124 mDataConnection.reset();
125 out.write(mFtpStatus.getResponse(226, request, mUser, null));
126 }
127
128
129 /**
130 * <code>APPE <SP> <pathname> <CRLF></code><br>
131 *
132 * This command causes the server-DTP to accept the data
133 * transferred via the data connection and to store the data in
134 * a file at the server site. If the file specified in the
135 * pathname exists at the server site, then the data shall be
136 * appended to that file; otherwise the file specified in the
137 * pathname shall be created at the server site.
138 */
139 public void doAPPE(FtpRequest request, FtpWriter out) throws IOException {
140
141 // reset state variables
142 resetState();
143
144 // argument check
145 if(!request.hasArgument()) {
146 out.write(mFtpStatus.getResponse(501, request, mUser, null));
147 return;
148 }
149
150 // get filenames
151 String fileName = request.getArgument();
152 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
153 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
154 File requestedFile = new File(physicalName);
155 String args[] = {fileName};
156
157 // check permission
158 if(!mUser.getVirtualDirectory().hasWritePermission(physicalName, true)) {
159 out.write(mFtpStatus.getResponse(450, request, mUser, args));
160 return;
161 }
162
163 // now transfer file data
164 out.write(mFtpStatus.getResponse(150, request, mUser, args));
165 InputStream is = null;
166 OutputStream os = null;
167 try {
168 Socket dataSoc = mDataConnection.getDataSocket();
169 if (dataSoc == null) {
170 out.write(mFtpStatus.getResponse(550, request, mUser, args));
171 return;
172 }
173
174 is = dataSoc.getInputStream();
175 RandomAccessFile raf = new RandomAccessFile(requestedFile, "rw");
176 raf.seek(raf.length());
177 os = mUser.getOutputStream( new FileOutputStream(raf.getFD()) );
178
179 StreamConnector msc = new StreamConnector(is, os);
180 msc.setMaxTransferRate(mUser.getMaxUploadRate());
181 msc.setObserver(this);
182 msc.connect();
183
184 if(msc.hasException()) {
185 out.write(mFtpStatus.getResponse(451, request, mUser, args));
186 }
187 else {
188 mConfig.getStatistics().setUpload(requestedFile, mUser, msc.getTransferredSize());
189 }
190
191 out.write(mFtpStatus.getResponse(226, request, mUser, args));
192 }
193 catch(IOException ex) {
194 out.write(mFtpStatus.getResponse(425, request, mUser, args));
195 }
196 finally {
197 IoUtils.close(is);
198 IoUtils.close(os);
199 mDataConnection.reset();
200 }
201 }
202
203
204 /**
205 * <code>CDUP <CRLF></code><br>
206 *
207 * This command is a special case of CWD, and is included to
208 * simplify the implementation of programs for transferring
209 * directory trees between operating systems having different
210 * syntaxes for naming the parent directory. The reply codes
211 * shall be identical to the reply codes of CWD.
212 */
213 public void doCDUP(FtpRequest request, FtpWriter out) throws IOException {
214
215 // reset state variables
216 resetState();
217
218 // change directory
219 if (mUser.getVirtualDirectory().changeDirectory("..")) {
220 String args[] = {mUser.getVirtualDirectory().getCurrentDirectory()};
221 out.write(mFtpStatus.getResponse(200, request, mUser, args));
222 }
223 else {
224 out.write(mFtpStatus.getResponse(431, request, mUser, null));
225 }
226 }
227
228
229 /**
230 * <code>CWD <SP> <pathname> <CRLF></code><br>
231 *
232 * This command allows the user to work with a different
233 * directory for file storage or retrieval without
234 * altering his login or accounting information. Transfer
235 * parameters are similarly unchanged. The argument is a
236 * pathname specifying a directory.
237 */
238 public void doCWD(FtpRequest request, FtpWriter out) throws IOException {
239
240 // reset state variables
241 resetState();
242
243 // get new directory name
244 String dirName = "/";
245 if (request.hasArgument()) {
246 dirName = request.getArgument();
247 }
248
249 // change directory
250 if (mUser.getVirtualDirectory().changeDirectory(dirName)) {
251 String args[] = {mUser.getVirtualDirectory().getCurrentDirectory()};
252 out.write(mFtpStatus.getResponse(200, request, mUser, args));
253 }
254 else {
255 out.write(mFtpStatus.getResponse(431, request, mUser, null));
256 }
257 }
258
259
260 /**
261 * <code>DELE <SP> <pathname> <CRLF></code><br>
262 *
263 * This command causes the file specified in the pathname to be
264 * deleted at the server site.
265 */
266 public void doDELE(FtpRequest request, FtpWriter out) throws IOException {
267
268 // reset state variables
269 resetState();
270
271 // argument check
272 if(!request.hasArgument()) {
273 out.write(mFtpStatus.getResponse(501, request, mUser, null));
274 return;
275 }
276
277 // get filenames
278 String fileName = request.getArgument();
279 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
280 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
281 File requestedFile = new File(physicalName);
282 String[] args = {fileName};
283
284 // check permission
285 if(!mUser.getVirtualDirectory().hasWritePermission(physicalName, true)) {
286 out.write(mFtpStatus.getResponse(450, request, mUser, args));
287 return;
288 }
289
290 // now delete
291 if(requestedFile.delete()) {
292 out.write(mFtpStatus.getResponse(250, request, mUser, args));
293 mConfig.getStatistics().setDelete(requestedFile, mUser);
294 }
295 else {
296 out.write(mFtpStatus.getResponse(450, request, mUser, args));
297 }
298 }
299
300
301 /**
302 * <code>HELP [<SP> <string>] <CRLF></code><br>
303 *
304 * This command shall cause the server to send helpful
305 * information regarding its implementation status over the
306 * control connection to the user. The command may take an
307 * argument (e.g., any command name) and return more specific
308 * information as a response.
309 */
310 public void doHELP(FtpRequest request, FtpWriter out) throws IOException {
311
312 // print global help
313 if(!request.hasArgument()) {
314 out.write(mFtpStatus.getResponse(214, null, mUser, null));
315 return;
316 }
317
318 // print command specific help
319 String ftpCmd = request.getArgument().toUpperCase();
320 String args[] = null;
321 FtpRequest tempRequest = new FtpRequest(ftpCmd);
322 out.write(mFtpStatus.getResponse(214, tempRequest, mUser, args));
323 return;
324 }
325
326
327 /**
328 * <code>LIST [<SP> <pathname>] <CRLF></code><br>
329 *
330 * This command causes a list to be sent from the server to the
331 * passive DTP. If the pathname specifies a directory or other
332 * group of files, the server should transfer a list of files
333 * in the specified directory. If the pathname specifies a
334 * file then the server should send current information on the
335 * file. A null argument implies the user's current working or
336 * default directory. The data transfer is over the data
337 * connection
338 */
339 public void doLIST(FtpRequest request, FtpWriter out) throws IOException {
340
341 // reset state variables
342 resetState();
343
344 out.write(mFtpStatus.getResponse(150, request, mUser, null));
345 Writer os = null;
346 try {
347 Socket dataSoc = mDataConnection.getDataSocket();
348 if (dataSoc == null) {
349 out.write(mFtpStatus.getResponse(550, request, mUser, null));
350 return;
351 }
352
353 os = new OutputStreamWriter(dataSoc.getOutputStream());
354
355 if (!mUser.getVirtualDirectory().printList(request.getArgument(), os)) {
356 out.write(mFtpStatus.getResponse(501, request, mUser, null));
357 }
358 else {
359 os.flush();
360 out.write(mFtpStatus.getResponse(226, request, mUser, null));
361 }
362 }
363 catch(IOException ex) {
364 out.write(mFtpStatus.getResponse(425, request, mUser, null));
365 }
366 finally {
367 IoUtils.close(os);
368 mDataConnection.reset();
369 }
370 }
371
372
373 /**
374 * <code>MDTM <SP> <pathname> <CRLF></code><br>
375 *
376 * Returns the date and time of when a file was modified.
377 */
378 public void doMDTM(FtpRequest request, FtpWriter out) throws IOException {
379
380 // argument check
381 if(!request.hasArgument()) {
382 out.write(mFtpStatus.getResponse(501, request, mUser, null));
383 return;
384 }
385
386 // reset state variables
387 resetState();
388
389 // get filenames
390 String fileName = request.getArgument();
391 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
392 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
393 File reqFile = new File(physicalName);
394
395 // now print date
396 if(reqFile.exists()) {
397 String args[] = {DATE_FMT.format(new Date(reqFile.lastModified()))};
398 out.write(mFtpStatus.getResponse(213, request, mUser, args));
399 }
400 else {
401 out.write(mFtpStatus.getResponse(550, request, mUser, null));
402 }
403 }
404
405
406 /**
407 * <code>MKD <SP> <pathname> <CRLF></code><br>
408 *
409 * This command causes the directory specified in the pathname
410 * to be created as a directory (if the pathname is absolute)
411 * or as a subdirectory of the current working directory (if
412 * the pathname is relative).
413 */
414 public void doMKD(FtpRequest request, FtpWriter out) throws IOException {
415
416 // reset state variables
417 resetState();
418
419 // argument check
420 if(!request.hasArgument()) {
421 out.write(mFtpStatus.getResponse(501, request, mUser, null));
422 return;
423 }
424
425 // get filenames
426 String fileName = request.getArgument();
427 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
428 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
429 String args[] = {fileName};
430
431 // check permission
432 if(!mUser.getVirtualDirectory().hasCreatePermission(physicalName, true)) {
433 out.write(mFtpStatus.getResponse(450, request, mUser, args));
434 return;
435 }
436
437 // now create directory
438 if(new File(physicalName).mkdirs()) {
439 out.write(mFtpStatus.getResponse(250, request, mUser, args));
440 }
441 else {
442 out.write(mFtpStatus.getResponse(450, request, mUser, args));
443 }
444 }
445
446
447 /**
448 * <code>MODE <SP> <mode-code> <CRLF></code><br>
449 *
450 * The argument is a single Telnet character code specifying
451 * the data transfer modes described in the Section on
452 * Transmission Modes.
453 */
454 public void doMODE(FtpRequest request, FtpWriter out) throws IOException {
455
456 // reset state variables
457 resetState();
458
459 // argument check
460 if(!request.hasArgument()) {
461 out.write(mFtpStatus.getResponse(501, request, mUser, null));
462 return;
463 }
464
465 if (mUser.setMode(request.getArgument().charAt(0))) {
466 out.write(mFtpStatus.getResponse(200, request, mUser, null));
467 }
468 else {
469 out.write(mFtpStatus.getResponse(504, request, mUser, null));
470 }
471 }
472
473
474 /**
475 * <code>NLST [<SP> <pathname>] <CRLF></code><br>
476 *
477 * This command causes a directory listing to be sent from
478 * server to user site. The pathname should specify a
479 * directory or other system-specific file group descriptor; a
480 * null argument implies the current directory. The server
481 * will return a stream of names of files and no other
482 * information.
483 */
484 public void doNLST(FtpRequest request, FtpWriter out) throws IOException {
485
486 // reset state variables
487 resetState();
488
489 out.write(mFtpStatus.getResponse(150, request, mUser, null));
490 Writer os = null;
491 try {
492 Socket dataSoc = mDataConnection.getDataSocket();
493 if (dataSoc == null) {
494 out.write(mFtpStatus.getResponse(550, request, mUser, null));
495 return;
496 }
497
498 os = new OutputStreamWriter(dataSoc.getOutputStream());
499
500 if (!mUser.getVirtualDirectory().printNList(request.getArgument(), os)) {
501 out.write(mFtpStatus.getResponse(501, request, mUser, null));
502 }
503 else {
504 os.flush();
505 out.write(mFtpStatus.getResponse(226, request, mUser, null));
506 }
507 }
508 catch(IOException ex) {
509 out.write(mFtpStatus.getResponse(425, request, mUser, null));
510 }
511 finally {
512 IoUtils.close(os);
513 mDataConnection.reset();
514 }
515 }
516
517
518 /**
519 * <code>NOOP <CRLF></code><br>
520 *
521 * This command does not affect any parameters or previously
522 * entered commands. It specifies no action other than that the
523 * server send an OK reply.
524 */
525 public void doNOOP(FtpRequest request, FtpWriter out) throws IOException {
526
527 // reset state variables
528 resetState();
529
530 out.write(mFtpStatus.getResponse(200, request, mUser, null));
531 }
532
533
534 /**
535 * <code>PASS <SP> <password> <CRLF></code><br>
536 *
537 * The argument field is a Telnet string specifying the user's
538 * password. This command must be immediately preceded by the
539 * user name command.
540 */
541 public void doPASS(FtpRequest request, FtpWriter out) throws IOException {
542
543 // set state variables
544 if(!mbUser) {
545 out.write(mFtpStatus.getResponse(500, request, mUser, null));
546 resetState();
547 return;
548 }
549 resetState();
550 mbPass = true;
551
552 // set user password and login
553 String pass = request.hasArgument() ? request.getArgument() : "";
554 mUser.setPassword(pass);
555
556 // login failure - close connection
557 String args[] = {mUser.getName()};
558 if (mConfig.getConnectionService().login(mUser)) {
559 out.write(mFtpStatus.getResponse(230, request, mUser, args));
560 }
561 else {
562 out.write(mFtpStatus.getResponse(530, request, mUser, args));
563 ConnectionService conService = mConfig.getConnectionService();
564 if (conService != null) {
565 conService.closeConnection(mUser.getSessionId());
566 }
567 }
568 }
569
570
571 /**
572 * <code>PASV <CRLF></code><br>
573 *
574 * This command requests the server-DTP to "listen" on a data
575 * port (which is not its default data port) and to wait for a
576 * connection rather than initiate one upon receipt of a
577 * transfer command. The response to this command includes the
578 * host and port address this server is listening on.
579 */
580 public void doPASV(FtpRequest request, FtpWriter out) throws IOException {
581
582 if (!mDataConnection.setPasvCommand()) {
583 out.write(mFtpStatus.getResponse(550, request, mUser, null));
584 return;
585 }
586
587 // InetAddress servAddr = mDataConnection.getInetAddress();
588 // if(servAddr == null) {
589 // servAddr = mConfig.getSelfAddress();
590 // }
591
592 InetAddress servAddr = mConfig.getServerAddress();
593
594 int servPort = mDataConnection.getPort();
595
596 String addrStr = servAddr.getHostAddress().replace( '.', ',' ) + ',' + (servPort>>8) + ',' + (servPort&0xFF);
597 jsd.gui.MessageBox.showMessageBox(addrStr);
598 String[] args = {addrStr};
599
600 out.write(mFtpStatus.getResponse(227, request, mUser, args));
601 if (!mDataConnection.listenPasvConnection()) {
602 out.write(mFtpStatus.getResponse(425, request, mUser, args));
603 }
604 }
605
606
607 /**
608 * <code>PORT <SP> <host-port> <CRLF></code><br>
609 *
610 * The argument is a HOST-PORT specification for the data port
611 * to be used in data connection. There are defaults for both
612 * the user and server data ports, and under normal
613 * circumstances this command and its reply are not needed. If
614 * this command is used, the argument is the concatenation of a
615 * 32-bit internet host address and a 16-bit TCP port address.
616 * This address information is broken into 8-bit fields and the
617 * value of each field is transmitted as a decimal number (in
618 * character string representation). The fields are separated
619 * by commas. A port command would be:
620 *
621 * PORT h1,h2,h3,h4,p1,p2
622 *
623 * where h1 is the high order 8 bits of the internet host address.
624 */
625 public void doPORT(FtpRequest request, FtpWriter out) throws IOException {
626
627 // reset state variables
628 resetState();
629
630 InetAddress clientAddr = null;
631 int clientPort = 0;
632
633 // argument check
634 if(!request.hasArgument()) {
635 out.write(mFtpStatus.getResponse(501, request, mUser, null));
636 return;
637 }
638
639 StringTokenizer st = new StringTokenizer(request.getArgument(), ",");
640 if(st.countTokens() != 6) {
641 out.write(mFtpStatus.getResponse(510, request, mUser, null));
642 return;
643 }
644
645 // get data server
646 String dataSrvName = st.nextToken() + '.' + st.nextToken() + '.' +
647 st.nextToken() + '.' + st.nextToken();
648 try {
649 clientAddr = InetAddress.getByName(dataSrvName);
650 }
651 catch(UnknownHostException ex) {
652 out.write(mFtpStatus.getResponse(553, request, mUser, null));
653 return;
654 }
655
656 // get data server port
657 try {
658 int hi = Integer.parseInt(st.nextToken());
659 int lo = Integer.parseInt(st.nextToken());
660 clientPort = (hi << 8) | lo;
661 }
662 catch(NumberFormatException ex) {
663 out.write(mFtpStatus.getResponse(552, request, mUser, null));
664 return;
665 }
666 mDataConnection.setPortCommand(clientAddr, clientPort);
667 out.write(mFtpStatus.getResponse(200, request, mUser, null));
668 System.out.println ("DEBUG: after setPortCommand()");
669 }
670
671
672 /**
673 * <code>PWD <CRLF></code><br>
674 *
675 * This command causes the name of the current working
676 * directory to be returned in the reply.
677 */
678 public void doPWD(FtpRequest request, FtpWriter out) throws IOException {
679
680 // reset state variables
681 resetState();
682 String args[] = {mUser.getVirtualDirectory().getCurrentDirectory()};
683 out.write(mFtpStatus.getResponse(257, request, mUser, args));
684 }
685
686
687 /**
688 * <code>QUIT <CRLF></code><br>
689 *
690 * This command terminates a USER and if file transfer is not
691 * in progress, the server closes the control connection.
692 */
693 public void doQUIT(FtpRequest request, FtpWriter out) throws IOException {
694
695 // reset state variables
696 resetState();
697
698 // and exit
699 out.write(mFtpStatus.getResponse(221, request, mUser, null));
700 ConnectionService conService = mConfig.getConnectionService();
701 if (conService != null) {
702 conService.closeConnection(mUser.getSessionId());
703 }
704 }
705
706
707 /**
708 * <code>REST <SP> <marker> <CRLF></code><br>
709 *
710 * The argument field represents the server marker at which
711 * file transfer is to be restarted. This command does not
712 * cause file transfer but skips over the file to the specified
713 * data checkpoint. This command shall be immediately followed
714 * by the appropriate FTP service command which shall cause
715 * file transfer to resume.
716 */
717 public void doREST(FtpRequest request, FtpWriter out) throws IOException {
718
719 // argument check
720 if(!request.hasArgument()) {
721 out.write(mFtpStatus.getResponse(501, request, mUser, null));
722 return;
723 }
724
725 // set state variables
726 resetState();
727 mlSkipLen = 0;
728 String skipNum = request.getArgument();
729 try {
730 mlSkipLen = Long.parseLong(skipNum);
731 }
732 catch(NumberFormatException ex) {
733 out.write(mFtpStatus.getResponse(501, request, mUser, null));
734 return;
735 }
736 if(mlSkipLen < 0) {
737 mlSkipLen = 0;
738 out.write(mFtpStatus.getResponse(501, request, mUser, null));
739 return;
740 }
741 mbReset = true;
742 out.write(mFtpStatus.getResponse(350, request, mUser, null));
743 }
744
745
746 /**
747 * <code>RETR <SP> <pathname> <CRLF></code><br>
748 *
749 * This command causes the server-DTP to transfer a copy of the
750 * file, specified in the pathname, to the server- or user-DTP
751 * at the other end of the data connection. The status and
752 * contents of the file at the server site shall be unaffected.
753 */
754 public void doRETR(FtpRequest request, FtpWriter out) throws IOException {
755
756 // set state variables
757 long skipLen = (mbReset) ? mlSkipLen : 0;
758 resetState();
759
760 // argument check
761 if(!request.hasArgument()) {
762 out.write(mFtpStatus.getResponse(501, request, mUser, null));
763 return;
764 }
765
766 // get filenames
767 String fileName = request.getArgument();
768 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
769 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
770 File requestedFile = new File(physicalName);
771 String args[] = {fileName};
772
773 // check permission
774 if(!mUser.getVirtualDirectory().hasReadPermission(physicalName, true)) {
775 out.write(mFtpStatus.getResponse(550, request, mUser, args));
776 return;
777 }
778
779 // now transfer file data
780 out.write(mFtpStatus.getResponse(150, request, mUser, null));
781 InputStream is = null;
782 OutputStream os = null;
783 try {
784 Socket dataSoc = mDataConnection.getDataSocket();
785 if (dataSoc == null) {
786 out.write(mFtpStatus.getResponse(550, request, mUser, args));
787 return;
788 }
789
790 os = mUser.getOutputStream(dataSoc.getOutputStream());
791
792 RandomAccessFile raf = new RandomAccessFile(requestedFile, "r");
793 raf.seek(skipLen);
794 is = new FileInputStream(raf.getFD());
795
796 StreamConnector msc = new StreamConnector(is, os);
797 msc.setMaxTransferRate(mUser.getMaxDownloadRate());
798 msc.setObserver(this);
799 msc.connect();
800
801 if(msc.hasException()) {
802 out.write(mFtpStatus.getResponse(451, request, mUser, args));
803 return;
804 }
805 else {
806 mConfig.getStatistics().setDownload(requestedFile, mUser, msc.getTransferredSize());
807 }
808
809 out.write(mFtpStatus.getResponse(226, request, mUser, null));
810 }
811 catch(IOException ex) {
812 out.write(mFtpStatus.getResponse(425, request, mUser, null));
813 }
814 finally {
815 IoUtils.close(is);
816 IoUtils.close(os);
817 mDataConnection.reset();
818 }
819 }
820
821
822 /**
823 * <code>RMD <SP> <pathname> <CRLF></code><br>
824 *
825 * This command causes the directory specified in the pathname
826 * to be removed as a directory (if the pathname is absolute)
827 * or as a subdirectory of the current working directory (if
828 * the pathname is relative).
829 */
830 public void doRMD(FtpRequest request, FtpWriter out) throws IOException {
831
832 // reset state variables
833 resetState();
834
835 // argument check
836 if(!request.hasArgument()) {
837 out.write(mFtpStatus.getResponse(501, request, mUser, null));
838 return;
839 }
840
841 // get file names
842 String fileName = request.getArgument();
843 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
844 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
845 File requestedFile = new File(physicalName);
846 String args[] = {fileName};
847
848 // check permission
849 if(!mUser.getVirtualDirectory().hasWritePermission(physicalName, true)) {
850 out.write(mFtpStatus.getResponse(450, request, mUser, args));
851 return;
852 }
853
854 // now delete
855 if(requestedFile.delete()) {
856 out.write(mFtpStatus.getResponse(250, request, mUser, args));
857 }
858 else {
859 out.write(mFtpStatus.getResponse(450, request, mUser, args));
860 }
861 }
862
863
864 /**
865 * <code>RNFR <SP> <pathname> <CRLF></code><br>
866 *
867 * This command specifies the old pathname of the file which is
868 * to be renamed. This command must be immediately followed by
869 * a "rename to" command specifying the new file pathname.
870 */
871 public void doRNFR(FtpRequest request, FtpWriter out) throws IOException {
872
873 // reset state variable
874 resetState();
875
876 // argument check
877 if(!request.hasArgument()) {
878 out.write(mFtpStatus.getResponse(501, request, mUser, null));
879 return;
880 }
881
882 // set state variable
883 mbRenFr = true;
884
885 // get filenames
886 String fileName = request.getArgument();
887 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
888 mstRenFr = mUser.getVirtualDirectory().getPhysicalName(fileName);
889 String args[] = {fileName};
890
891 out.write(mFtpStatus.getResponse(350, request, mUser, args));
892 }
893
894
895 /**
896 * <code>RNTO <SP> <pathname> <CRLF></code><br>
897 *
898 * This command specifies the new pathname of the file
899 * specified in the immediately preceding "rename from"
900 * command. Together the two commands cause a file to be
901 * renamed.
902 */
903 public void doRNTO(FtpRequest request, FtpWriter out) throws IOException {
904
905 // argument check
906 if(!request.hasArgument()) {
907 resetState();
908 out.write(mFtpStatus.getResponse(501, request, mUser, null));
909 return;
910 }
911
912 // set state variables
913 if((!mbRenFr) || (mstRenFr == null)) {
914 resetState();
915 out.write(mFtpStatus.getResponse(100, request, mUser, null));
916 return;
917 }
918
919 // get filenames
920 String fromFileStr = mUser.getVirtualDirectory().getVirtualName(mstRenFr);
921 String toFileStr = request.getArgument();
922 toFileStr = mUser.getVirtualDirectory().getAbsoluteName(toFileStr);
923 String physicalToFileStr = mUser.getVirtualDirectory().getPhysicalName(toFileStr);
924 File fromFile = new File(mstRenFr);
925 File toFile = new File(physicalToFileStr);
926 String args[] = {fromFileStr, toFileStr};
927
928 resetState();
929
930 // check permission
931 if(!mUser.getVirtualDirectory().hasCreatePermission(physicalToFileStr, true)) {
932 out.write(mFtpStatus.getResponse(553, request, mUser, null));
933 return;
934 }
935
936 // now rename
937 if(fromFile.renameTo(toFile)) {
938 out.write(mFtpStatus.getResponse(250, request, mUser, args));
939 }
940 else {
941 out.write(mFtpStatus.getResponse(553, request, mUser, args));
942 }
943 }
944
945
946 /**
947 * <code>SITE <SP> <string> <CRLF></code><br>
948 *
949 * This command is used by the server to provide services
950 * specific to his system that are essential to file transfer
951 * but not sufficiently universal to be included as commands in
952 * the protocol.
953 */
954 public void doSITE(FtpRequest request, FtpWriter out) throws IOException {
955 SiteCommandHandler siteCmd = new SiteCommandHandler( mConfig, mUser );
956 out.write( siteCmd.getResponse(request) );
957 }
958
959
960 /**
961 * <code>SIZE <SP> <pathname> <CRLF></code><br>
962 *
963 * Returns the size of the file in bytes.
964 */
965 public void doSIZE(FtpRequest request, FtpWriter out) throws IOException {
966
967 // argument check
968 if(!request.hasArgument()) {
969 out.write(mFtpStatus.getResponse(501, request, mUser, null));
970 return;
971 }
972
973 // reset state variables
974 resetState();
975
976 // get filenames
977 String fileName = request.getArgument();
978 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
979 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
980 File reqFile = new File(physicalName);
981
982 // print file size
983 if(reqFile.exists()) {
984 String args[] = {String.valueOf(reqFile.length())};
985 out.write(mFtpStatus.getResponse(213, request, mUser, args));
986 }
987 else {
988 out.write(mFtpStatus.getResponse(550, request, mUser, null));
989 }
990 }
991
992
993 /**
994 * <code>STAT [<SP> <pathname>] <CRLF></code><br>
995 *
996 * This command shall cause a status response to be sent over
997 * the control connection in the form of a reply.
998 */
999 public void doSTAT(FtpRequest request, FtpWriter out) throws IOException {
1000 String args[] = {
1001 mConfig.getSelfAddress().getHostAddress(),
1002 mControlSocket.getInetAddress().getHostAddress(),
1003 mUser.getName()
1004 };
1005
1006 out.write(mFtpStatus.getResponse(211, request, mUser, args));
1007 }
1008
1009
1010 /**
1011 * <code>STOR <SP> <pathname> <CRLF></code><br>
1012 *
1013 * This command causes the server-DTP to accept the data
1014 * transferred via the data connection and to store the data as
1015 * a file at the server site. If the file specified in the
1016 * pathname exists at the server site, then its contents shall
1017 * be replaced by the data being transferred. A new file is
1018 * created at the server site if the file specified in the
1019 * pathname does not already exist.
1020 */
1021 public void doSTOR(FtpRequest request, FtpWriter out) throws IOException {
1022
1023 // set state variables
1024 long skipLen = (mbReset) ? mlSkipLen : 0;
1025 resetState();
1026
1027 // argument check
1028 if(!request.hasArgument()) {
1029 out.write(mFtpStatus.getResponse(501, request, mUser, null));
1030 return;
1031 }
1032
1033 // get filenames
1034 String fileName = request.getArgument();
1035 fileName = mUser.getVirtualDirectory().getAbsoluteName(fileName);
1036 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
1037 File requestedFile = new File(physicalName);
1038
1039 // get permission
1040 if(!mUser.getVirtualDirectory().hasCreatePermission(physicalName, true)) {
1041 out.write(mFtpStatus.getResponse(550, request, mUser, null));
1042 return;
1043 }
1044
1045 // now transfer file data
1046 out.write(mFtpStatus.getResponse(150, request, mUser, null));
1047 InputStream is = null;
1048 OutputStream os = null;
1049 try {
1050 Socket dataSoc = mDataConnection.getDataSocket();
1051 if (dataSoc == null) {
1052 out.write(mFtpStatus.getResponse(550, request, mUser, null));
1053 return;
1054 }
1055
1056 is = dataSoc.getInputStream();
1057
1058 RandomAccessFile raf = new RandomAccessFile(requestedFile, "rw");
1059 raf.seek(skipLen);
1060 os = mUser.getOutputStream( new FileOutputStream(raf.getFD()) );
1061
1062 StreamConnector msc = new StreamConnector(is, os);
1063 msc.setMaxTransferRate(mUser.getMaxUploadRate());
1064 msc.setObserver(this);
1065 msc.connect();
1066
1067 if(msc.hasException()) {
1068 out.write(mFtpStatus.getResponse(451, request, mUser, null));
1069 return;
1070 }
1071 else {
1072 mConfig.getStatistics().setUpload(requestedFile, mUser, msc.getTransferredSize());
1073 }
1074
1075 out.write(mFtpStatus.getResponse(226, request, mUser, null));
1076 }
1077 catch(IOException ex) {
1078 out.write(mFtpStatus.getResponse(425, request, mUser, null));
1079 }
1080 finally {
1081 IoUtils.close(is);
1082 IoUtils.close(os);
1083 mDataConnection.reset();
1084 }
1085 }
1086
1087
1088 /**
1089 * <code>STOU <CRLF></code><br>
1090 *
1091 * This command behaves like STOR except that the resultant
1092 * file is to be created in the current directory under a name
1093 * unique to that directory. The 250 Transfer Started response
1094 * must include the name generated.
1095 */
1096 public void doSTOU(FtpRequest request, FtpWriter out) throws IOException {
1097
1098 // reset state variables
1099 resetState();
1100
1101 // get filenames
1102 String fileName = mUser.getVirtualDirectory().getAbsoluteName("ftp.dat");
1103 String physicalName = mUser.getVirtualDirectory().getPhysicalName(fileName);
1104 File requestedFile = new File(physicalName);
1105 requestedFile = IoUtils.getUniqueFile(requestedFile);
1106 fileName = mUser.getVirtualDirectory().getVirtualName(requestedFile.getAbsolutePath());
1107 String args[] = {fileName};
1108
1109 // check permission
1110 if(!mUser.getVirtualDirectory().hasCreatePermission(fileName, false)) {
1111 out.write(mFtpStatus.getResponse(550, request, mUser, null));
1112 return;
1113 }
1114
1115 // now transfer file data
1116 out.write(mFtpStatus.getResponse(150, request, mUser, null));
1117 InputStream is = null;
1118 OutputStream os = null;
1119 try {
1120 Socket dataSoc = mDataConnection.getDataSocket();
1121 if (dataSoc == null) {
1122 out.write(mFtpStatus.getResponse(550, request, mUser, args));
1123 return;
1124 }
1125
1126
1127 is = dataSoc.getInputStream();
1128 os = mUser.getOutputStream( new FileOutputStream(requestedFile) );
1129
1130 StreamConnector msc = new StreamConnector(is, os);
1131 msc.setMaxTransferRate(mUser.getMaxUploadRate());
1132 msc.setObserver(this);
1133 msc.connect();
1134
1135 if(msc.hasException()) {
1136 out.write(mFtpStatus.getResponse(451, request, mUser, null));
1137 return;
1138 }
1139 else {
1140 mConfig.getStatistics().setUpload(requestedFile, mUser, msc.getTransferredSize());
1141 }
1142
1143 out.write(mFtpStatus.getResponse(226, request, mUser, null));
1144 mDataConnection.reset();
1145 out.write(mFtpStatus.getResponse(250, request, mUser, args));
1146 }
1147 catch(IOException ex) {
1148 out.write(mFtpStatus.getResponse(425, request, mUser, null));
1149 }
1150 finally {
1151 IoUtils.close(is);
1152 IoUtils.close(os);
1153 mDataConnection.reset();
1154 }
1155 }
1156
1157
1158 /**
1159 * <code>STRU <SP> <structure-code> <CRLF></code><br>
1160 *
1161 * The argument is a single Telnet character code specifying
1162 * file structure.
1163 */
1164 public void doSTRU(FtpRequest request, FtpWriter out) throws IOException {
1165
1166 // reset state variables
1167 resetState();
1168
1169 // argument check
1170 if(!request.hasArgument()) {
1171 out.write(mFtpStatus.getResponse(501, request, mUser, null));
1172 return;
1173 }
1174
1175 if (mUser.setStructure(request.getArgument().charAt(0))) {
1176 out.write(mFtpStatus.getResponse(200, request, mUser, null));
1177 }
1178 else {
1179 out.write(mFtpStatus.getResponse(504, request, mUser, null));
1180 }
1181 }
1182
1183
1184 /**
1185 * <code>SYST <CRLF></code><br>
1186 *
1187 * This command is used to find out the type of operating
1188 * system at the server.
1189 */
1190 public void doSYST(FtpRequest request, FtpWriter out) throws IOException {
1191
1192 // reset state variables
1193 resetState();
1194
1195 String args[] = {mConfig.getSystemName()};
1196 out.write(mFtpStatus.getResponse(215, request, mUser, args));
1197 }
1198
1199
1200 /**
1201 * <code>TYPE <SP> <type-code> <CRLF></code><br>
1202 *
1203 * The argument specifies the representation type.
1204 */
1205 public void doTYPE(FtpRequest request, FtpWriter out) throws IOException {
1206
1207 // reset state variables
1208 resetState();
1209
1210 // get type from argument
1211 char type = 'A';
1212 if (request.hasArgument()){
1213 type = request.getArgument().charAt(0);
1214 }
1215
1216 // set it
1217 if (mUser.setType(type)) {
1218 out.write(mFtpStatus.getResponse(200, request, mUser, null));
1219 }
1220 else {
1221 out.write(mFtpStatus.getResponse(504, request, mUser, null));
1222 }
1223 }
1224
1225
1226 /**
1227 * <code>USER <SP> <username> <CRLF></code><br>
1228 *
1229 * The argument field is a Telnet string identifying the user.
1230 * The user identification is that which is required by the
1231 * server for access to its file system. This command will
1232 * normally be the first command transmitted by the user after
1233 * the control connections are made.
1234 */
1235 public void doUSER(FtpRequest request, FtpWriter out) throws IOException {
1236
1237 // set state variables
1238 resetState();
1239
1240 // argument check
1241 if(!request.hasArgument()) {
1242 out.write(mFtpStatus.getResponse(501, request, mUser, null));
1243 return;
1244 }
1245
1246 // check user login status
1247 mbUser = true;
1248 if(mUser.hasLoggedIn()) {
1249 if(mUser.getName().equals(request.getArgument())) {
1250 out.write(mFtpStatus.getResponse(230, request, mUser, null));
1251 return;
1252 }
1253 else {
1254 mConfig.getConnectionService().closeConnection(mUser.getSessionId());
1255 }
1256 }
1257
1258 // set user name and send appropriate message
1259 mUser.setName(request.getArgument());
1260 if(mUser.getIsAnonymous()) {
1261 if(mConfig.isAnonymousLoginAllowed()) {
1262 FtpRequest anoRequest = new FtpRequest(mUser.getName());
1263 out.write(mFtpStatus.getResponse(331, anoRequest, mUser, null));
1264 }
1265 else {
1266 out.write(mFtpStatus.getResponse(530, request, mUser, null));
1267 ConnectionService conService = mConfig.getConnectionService();
1268 if (conService != null) {
1269 conService.closeConnection(mUser.getSessionId());
1270 }
1271 }
1272 }
1273 else {
1274 out.write(mFtpStatus.getResponse(331, request, mUser, null));
1275 }
1276 }
1277
1278}
1279