Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;CRLF&gt;</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  &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 [&lt;SP&gt; <string>] &lt;CRLF&gt;</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 [&lt;SP&gt; &lt;pathname&gt;] &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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  &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;SP&gt; <mode-code> &lt;CRLF&gt;</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 [&lt;SP&gt; &lt;pathname&gt;] &lt;CRLF&gt;</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 &lt;CRLF&gt;</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 &lt;SP&gt; <password> &lt;CRLF&gt;</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 &lt;CRLF&gt;</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 &lt;SP&gt; <host-port> &lt;CRLF&gt;</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  &lt;CRLF&gt;</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 &lt;CRLF&gt;</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 &lt;SP&gt; <marker> &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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  &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;SP&gt; <string> &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 [&lt;SP&gt; &lt;pathname&gt;] &lt;CRLF&gt;</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 &lt;SP&gt; &lt;pathname&gt; &lt;CRLF&gt;</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 &lt;CRLF&gt;</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 &lt;SP&gt; &lt;structure-code&gt; &lt;CRLF&gt;</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 &lt;CRLF&gt;</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 &lt;SP&gt; &lt;type-code&gt; &lt;CRLF&gt;</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 &lt;SP&gt; &lt;username&gt; &lt;CRLF&gt;</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