You are viewing a plain text version of this content. The canonical link for it is here.
Posted to ftpserver-commits@incubator.apache.org by ng...@apache.org on 2007/01/07 21:36:40 UTC

svn commit: r493852 [2/2] - in /incubator/ftpserver/trunk/core/src: java/org/apache/ftpserver/ java/org/apache/ftpserver/command/ java/org/apache/ftpserver/interfaces/ java/org/apache/ftpserver/listener/ java/org/apache/ftpserver/listener/io/ test/org/...

Added: incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOConnection.java
URL: http://svn.apache.org/viewvc/incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOConnection.java?view=auto&rev=493852
==============================================================================
--- incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOConnection.java (added)
+++ incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOConnection.java Sun Jan  7 13:36:35 2007
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */  
+
+package org.apache.ftpserver.listener.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+import javax.net.ssl.SSLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.ftpserver.FtpDataConnection;
+import org.apache.ftpserver.FtpRequestImpl;
+import org.apache.ftpserver.FtpSessionImpl;
+import org.apache.ftpserver.FtpWriter;
+import org.apache.ftpserver.ftplet.DataType;
+import org.apache.ftpserver.ftplet.FileSystemView;
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.FtpRequest;
+import org.apache.ftpserver.ftplet.FtpSession;
+import org.apache.ftpserver.ftplet.Ftplet;
+import org.apache.ftpserver.ftplet.FtpletEnum;
+import org.apache.ftpserver.ftplet.User;
+import org.apache.ftpserver.interfaces.Command;
+import org.apache.ftpserver.interfaces.CommandFactory;
+import org.apache.ftpserver.interfaces.FtpServerContext;
+import org.apache.ftpserver.interfaces.IpRestrictor;
+import org.apache.ftpserver.interfaces.ServerFtpStatistics;
+import org.apache.ftpserver.interfaces.Ssl;
+import org.apache.ftpserver.listener.Connection;
+import org.apache.ftpserver.listener.ConnectionManager;
+import org.apache.ftpserver.listener.ConnectionObserver;
+import org.apache.ftpserver.util.IoUtils;
+
+
+/**
+ * This is a generic request handler. It delegates 
+ * the request to appropriate method in subclass.
+ *
+ * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
+ */
+public 
+class IOConnection implements Connection {
+    
+    private FtpServerContext serverContext;
+    private Log log;
+    
+    private Socket controlSocket;
+    private FtpSessionImpl session;
+    private FtpWriter writer;
+    private BufferedReader reader;
+    private boolean isConnectionClosed;
+    
+    
+    /**
+     * Constructor - set the control socket.
+     */
+    public IOConnection(FtpServerContext serverContext, Socket controlSocket) throws IOException {
+        this.serverContext = serverContext;
+        this.controlSocket = controlSocket;
+        log = this.serverContext.getLogFactory().getInstance(getClass());
+        
+        // data connection object
+        FtpDataConnection dataCon = new FtpDataConnection();
+        dataCon.setServerContext(this.serverContext);
+        dataCon.setServerControlAddress(controlSocket.getLocalAddress());
+        
+        // reader object
+        session = new FtpSessionImpl();
+        session.setClientAddress(this.controlSocket.getInetAddress());
+        session.setFtpDataConnection(dataCon);
+        
+        // writer object
+        writer = new FtpWriter();
+        writer.setControlSocket(this.controlSocket);
+        writer.setServerContext(this.serverContext);
+        writer.setFtpSession(session);
+    }
+    
+    /**
+     * Set observer.
+     */
+    public void setObserver(ConnectionObserver observer) {
+        
+        // set writer observer
+        FtpWriter writer = this.writer;
+        if(writer != null) {
+            writer.setObserver(observer);
+        }
+        
+        // set request observer
+        FtpSessionImpl session = this.session;
+        if(session != null) {
+            session.setObserver(observer);
+        }
+    }   
+    
+    /**
+     * Get the configuration object.
+     */
+    public FtpServerContext getServerContext() {
+        return serverContext;
+    }
+
+        
+    /**
+     * Get request.
+     */
+    public FtpSession getSession() {
+        return session;
+    }
+    
+    /**
+     * Server one FTP client connection.
+     */
+    public void run() {
+        if(session == null ) {
+            return;
+        }
+        if(serverContext == null) {
+        	return;
+        }
+        
+        InetAddress clientAddr = session.getRemoteAddress();
+        ConnectionManager conManager = serverContext.getConnectionManager();
+        Ftplet ftpletContainer = serverContext.getFtpletContainer();
+        
+        if(conManager == null) {
+        	return;
+        }
+        if(ftpletContainer == null) {
+        	return;
+        }
+        try {
+            
+            // write log message
+            String hostAddress = clientAddr.getHostAddress();
+            log.info("Open connection - " + hostAddress);
+            
+            // notify ftp statistics
+            ServerFtpStatistics ftpStat = (ServerFtpStatistics)serverContext.getFtpStatistics();
+            ftpStat.setOpenConnection(this);
+            
+            // call Ftplet.onConnect() method
+            boolean isSkipped = false;
+
+            FtpletEnum ftpletRet = ftpletContainer.onConnect(session, writer);
+            if(ftpletRet == FtpletEnum.RET_SKIP) {
+                isSkipped = true;
+            }
+            else if(ftpletRet == FtpletEnum.RET_DISCONNECT) {
+                conManager.closeConnection(this);
+                return;
+            }
+            
+            if(!isSkipped) {
+
+                // IP permission check
+                IpRestrictor ipRestrictor = serverContext.getIpRestrictor();
+                if( !ipRestrictor.hasPermission(clientAddr) ) {
+                    log.warn("No permission to access from " + hostAddress);
+                    writer.send(530, "ip.restricted", null);
+                    return;
+                }
+                
+                // connection limit check
+                int maxConnections = conManager.getMaxConnections();
+                
+                if(maxConnections != 0 && ftpStat.getCurrentConnectionNumber() > maxConnections) {
+                    log.warn("Maximum connection limit reached.");
+                    writer.send(530, "connection.limit", null);
+                    return;
+                }
+                
+                // everything is fine - go ahead 
+                writer.send(220, null, null);
+            }
+            
+            reader = new BufferedReader(new InputStreamReader(controlSocket.getInputStream(), "UTF-8"));
+            do {
+                notifyObserver();
+                String commandLine = reader.readLine();
+                
+                // test command line
+                if(commandLine == null) {
+                    break;
+                }
+                commandLine = commandLine.trim();
+                if(commandLine.equals("")) {
+                    continue;
+                }
+                
+                // parse and check permission
+                FtpRequestImpl request = new FtpRequestImpl(commandLine);
+                session.setCurrentRequest(request);
+                
+                if(!hasPermission(request)) {
+                    writer.send(530, "permission", null);
+                    continue;
+                }
+
+                // execute command
+                service(request, session, writer);
+                
+                if(session != null) {
+                    session.setCurrentRequest(null);
+                }
+            }
+            while(!isConnectionClosed);
+        } catch(SocketException ex) {
+            // socket closed - no need to do anything
+        } catch(SSLException ex) {
+            log.warn("The client did not initiate the SSL connection correctly", ex);
+        } catch(Exception ex) {
+            log.warn("RequestHandler.run()", ex);
+        }
+        finally {
+            // close all resources if not done already
+            if(!isConnectionClosed) {
+                 conManager.closeConnection(this);
+            }
+        }
+    }
+    
+    /**
+     * Notify connection manager observer.
+     */
+    protected void notifyObserver() {
+        session.updateLastAccessTime();
+        serverContext.getConnectionManager().updateConnection(this);
+    }
+
+    /**
+     * Execute the ftp command.
+     */
+    public void service(FtpRequest request, FtpSessionImpl session, FtpWriter out) throws IOException, FtpException {
+        try {
+            String commandName = request.getCommand();
+            CommandFactory commandFactory = serverContext.getCommandFactory();
+            Command command = commandFactory.getCommand(commandName);
+            if(command != null) {
+                command.execute(this, request, session, out);
+            }
+            else {
+                out.send(502, "not.implemented", null);
+            }
+        }
+        catch(Exception ex) {
+            
+            // send error reply
+            try { 
+                out.send(550, null, null);
+            }
+            catch(Exception ex1) {
+            }
+            
+            if (ex instanceof java.io.IOException) {
+               throw (IOException)ex;
+            }
+            else {
+                log.warn("RequestHandler.service()", ex);
+            }
+        }
+    }
+    
+    /**
+     * Close connection. This is called by the connection service.
+     */
+    public void close() {
+        
+        // check whether already closed or not
+        synchronized(this) {
+            if(isConnectionClosed) {
+                return;
+            }
+            isConnectionClosed = true;
+        }
+        
+        // call Ftplet.onDisconnect() method.
+        try {
+            Ftplet ftpletContainer = serverContext.getFtpletContainer();
+            ftpletContainer.onDisconnect(session, writer);
+        }
+        catch(Exception ex) {
+            log.warn("RequestHandler.close()", ex);
+        }
+
+        // notify statistics object and close request
+        ServerFtpStatistics ftpStat = (ServerFtpStatistics)serverContext.getFtpStatistics();
+
+        if(session != null) {
+            
+            // log message
+            User user = session.getUser();
+            String userName = user != null ? user.getName() : "<Not logged in>";
+            InetAddress clientAddr = session.getRemoteAddress(); 
+            log.info("Close connection : " + clientAddr.getHostAddress() + " - " + userName);
+            
+            // logout if necessary and notify statistics
+            if(session.isLoggedIn()) {
+                session.setLogout();
+                ftpStat.setLogout(this);
+            }
+            ftpStat.setCloseConnection(this);
+            
+            // clear request
+            session.clear();
+            session.setObserver(null);
+            session.getFtpDataConnection().dispose();
+            FileSystemView fview = session.getFileSystemView();
+            if(fview != null) {
+                fview.dispose();
+            }
+            session = null;
+        }
+                
+        // close ftp writer
+        FtpWriter writer = this.writer;
+        if(writer != null) {
+            writer.setObserver(null);
+            writer.close();
+            writer = null;
+        }
+        
+        // close buffered reader
+        BufferedReader reader = this.reader;
+        if(reader != null) {
+            IoUtils.close(reader);
+            reader = null;
+        }
+        
+        // close control socket
+        Socket controlSocket = this.controlSocket;
+        if (controlSocket != null) {
+            try {
+                controlSocket.close();
+            }
+            catch(Exception ex) {
+                log.warn("RequestHandler.close()", ex);
+            }
+            controlSocket = null;
+        }
+    }
+
+    /**
+     * Check user permission to execute ftp command. 
+     */
+    protected boolean hasPermission(FtpRequest request) {
+        String cmd = request.getCommand();
+        if(cmd == null) {
+            return false;
+        }
+        return session.isLoggedIn() ||
+               cmd.equals("USER")   || 
+		       cmd.equals("PASS")   ||
+		       cmd.equals("QUIT")   ||
+		       cmd.equals("AUTH")   ||
+		       cmd.equals("HELP")   ||
+		       cmd.equals("SYST")   ||
+		       cmd.equals("FEAT")   ||
+		       cmd.equals("PBSZ")   ||
+		       cmd.equals("PROT")   ||
+	           cmd.equals("LANG")   ||
+	           cmd.equals("ACCT");
+    }    
+    
+    /**
+     * Transfer data.
+     */
+    public final long transfer(InputStream in, 
+                               OutputStream out,
+                               int maxRate) throws IOException {
+        
+        BufferedInputStream bis = IoUtils.getBufferedInputStream(in);
+        BufferedOutputStream bos = IoUtils.getBufferedOutputStream( out );
+        
+        boolean isAscii = session.getDataType() == DataType.ASCII;
+        long startTime = System.currentTimeMillis();
+        long transferredSize = 0L;
+        byte[] buff = new byte[4096];
+        
+        while(true) {
+            
+            // if current rate exceeds the max rate, sleep for 50ms 
+            // and again check the current transfer rate
+            if(maxRate > 0) {
+                
+                // prevent "divide by zero" exception
+                long interval = System.currentTimeMillis() - startTime;
+                if(interval == 0) {
+                    interval = 1;
+                }
+                
+                // check current rate
+                long currRate = (transferredSize*1000L)/interval;
+                if(currRate > maxRate) {
+                    try { Thread.sleep(50); } catch(InterruptedException ex) {break;}
+                    continue;
+                }
+            }
+            
+            // read data
+            int count = bis.read(buff);
+            if(count == -1) {
+                break;
+            }
+            
+            // write data
+            // if ascii, replace \n by \r\n
+            if(isAscii) {
+                for(int i=0; i<count; ++i) {
+                    byte b = buff[i];
+                    if(b == '\n') {
+                        bos.write('\r');
+                    }
+                    bos.write(b);
+                }
+            }
+            else {
+                bos.write(buff, 0, count);
+            }
+            
+            transferredSize += count;
+            notifyObserver();
+        }
+        
+        return transferredSize;
+    }       
+    
+    /**
+     * Create secure socket.
+     */
+    public void createSecureSocket(String protocol) throws Exception {
+
+        // change socket to SSL socket
+        Ssl ssl = serverContext.getSocketFactory().getSSL();
+        if(ssl == null) {
+            throw new FtpException("Socket factory SSL not configured");
+        }
+        Socket ssoc = ssl.createSocket(protocol, controlSocket, false);
+        
+        // change streams
+        reader = new BufferedReader(new InputStreamReader(ssoc.getInputStream(), "UTF-8"));
+        writer.setControlSocket(ssoc);
+        
+        // set control socket
+        controlSocket = ssoc;
+    }
+    
+    /**
+     * Retrive the socket used for the control channel
+     * @return The control socket
+     */
+    public Socket getControlSocket() {
+        return controlSocket;
+    }
+}

Propchange: incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOListener.java
URL: http://svn.apache.org/viewvc/incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOListener.java?view=auto&rev=493852
==============================================================================
--- incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOListener.java (added)
+++ incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOListener.java Sun Jan  7 13:36:35 2007
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ 
+
+package org.apache.ftpserver.listener.io;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+import org.apache.commons.logging.Log;
+import org.apache.ftpserver.interfaces.FtpServerContext;
+import org.apache.ftpserver.listener.Connection;
+import org.apache.ftpserver.listener.ConnectionManager;
+import org.apache.ftpserver.listener.Listener;
+
+/**
+ * The default {@link Listener} implementation.
+ *
+ */
+public class IOListener implements Listener, Runnable {
+
+    private Log log;
+    
+    private FtpServerContext serverContext;
+
+    private ServerSocket serverSocket;
+
+    private Thread listenerThread;
+
+    private boolean suspended = false;
+
+    /**
+     * Constructs a listener based on the configuration object
+     * 
+     * @param serverContext Configuration for the listener
+     */
+    public IOListener(FtpServerContext serverContext) {
+        this.serverContext = serverContext;
+        
+        log = serverContext.getLogFactory().getInstance(getClass());
+    }
+
+    /**
+     * @see Listener#start()
+     */
+    public void start() throws Exception {
+        serverSocket = serverContext.getSocketFactory().createServerSocket();
+
+        listenerThread = new Thread(this);
+        listenerThread.start();
+
+    }
+
+    /**
+     * The main thread method for the listener
+     */
+    public void run() {
+        if(serverSocket == null) {
+            throw new IllegalStateException("start() must be called before run()");
+        }
+        
+        log.info("Listener started on port " + serverSocket.getLocalPort());
+
+        // serverContext might be null if stop has been called
+        if (serverContext == null) {
+            return;
+        }
+
+        ConnectionManager conManager = serverContext.getConnectionManager();
+        
+        while (true) {
+            try {
+
+                // closed - return
+                if (serverSocket == null) {
+                    return;
+                }
+
+                // accept new connection .. if suspended
+                // close immediately.
+                Socket soc = serverSocket.accept();
+
+                if (suspended) {
+                    try {
+                        soc.close();
+                    } catch (Exception ex) {
+                        // ignore
+                    }
+                    continue;
+                }
+
+                Connection connection = new IOConnection(serverContext, soc);
+                conManager.newConnection(connection);
+            } catch (SocketException ex) {
+                return;
+            } catch (Exception ex) {
+                log.debug("Listener ending", ex);
+                return;
+            }
+        }
+    }
+
+    /**
+     * @see Listener#stop()
+     */
+    public synchronized void stop() {
+        // close server socket
+        if (serverSocket != null) {
+
+            try {
+                serverSocket.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+            serverSocket = null;
+        }
+
+        listenerThread.interrupt();
+
+        // wait for the runner thread to terminate
+        if (listenerThread != null && listenerThread.isAlive()) {
+
+            try {
+                listenerThread.join();
+            } catch (InterruptedException ex) {
+            }
+            listenerThread = null;
+        }
+    }
+
+    /**
+     * @see Listener#isStopped()
+     */
+    public boolean isStopped() {
+        return listenerThread == null;
+
+    }
+
+    /**
+     * @see Listener#isSuspended()
+     */
+    public boolean isSuspended() {
+        return suspended;
+    }
+
+    /**
+     * @see Listener#resume()
+     */
+    public void resume() {
+        suspended = false;
+    }
+
+    /**
+     * @see Listener#suspend()
+     */
+    public void suspend() {
+        suspended = true;
+    }
+}
\ No newline at end of file

Propchange: incubator/ftpserver/trunk/core/src/java/org/apache/ftpserver/listener/io/IOListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/FtpRequestImplTest.java
URL: http://svn.apache.org/viewvc/incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/FtpRequestImplTest.java?view=diff&rev=493852&r1=493851&r2=493852
==============================================================================
--- incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/FtpRequestImplTest.java (original)
+++ incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/FtpRequestImplTest.java Sun Jan  7 13:36:35 2007
@@ -33,6 +33,24 @@
         assertNull(request.getArgument());
     }
 
+    public void testCommandWithLeadingWhitespace() {
+        FtpRequestImpl request = new FtpRequestImpl("\rfoo");
+        
+        assertEquals("foo", request.getRequestLine());
+        assertEquals("FOO", request.getCommand());
+        assertFalse(request.hasArgument());
+        assertNull(request.getArgument());
+    }
+
+    public void testCommandWithTrailingWhitespace() {
+        FtpRequestImpl request = new FtpRequestImpl("foo\r");
+        
+        assertEquals("foo", request.getRequestLine());
+        assertEquals("FOO", request.getCommand());
+        assertFalse(request.hasArgument());
+        assertNull(request.getArgument());
+    }
+
     public void testCommandAndSingleArgument() {
         FtpRequestImpl request = new FtpRequestImpl("foo bar");
         

Modified: incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/interfaces/ServerFtpStatisticsTestTemplate.java
URL: http://svn.apache.org/viewvc/incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/interfaces/ServerFtpStatisticsTestTemplate.java?view=diff&rev=493852&r1=493851&r2=493852
==============================================================================
--- incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/interfaces/ServerFtpStatisticsTestTemplate.java (original)
+++ incubator/ftpserver/trunk/core/src/test/org/apache/ftpserver/interfaces/ServerFtpStatisticsTestTemplate.java Sun Jan  7 13:36:35 2007
@@ -29,6 +29,8 @@
 
 import org.apache.ftpserver.FtpStatisticsImpl;
 import org.apache.ftpserver.ftplet.FtpSession;
+import org.apache.ftpserver.listener.Connection;
+import org.apache.ftpserver.listener.ConnectionObserver;
 
 public abstract class ServerFtpStatisticsTestTemplate extends TestCase {