Find attached a pacth to use a WhiteList instead of BlackList.

It's very easy to implement, yet since I do not use spring nor I'm very
familiar with XSD's I need someone else to implement the Spring side.

Also this might be included in Mina? Else I would need to rename package to
FtpServer one.

For using as embeded I made:

// Create listener
ListenerFactory factory = new ListenerFactory();
// set the port of the listener
factory.setPort(2221);
// set WhiteList subnets
factory.setAllowedSubnets(customWhiteList());



My customWhiteList does:

private List<Subnet> customWhiteList(){
String[] blocks = {"127.0.0.0/16", "192.168.1.0/8"};
List<Subnet> subnets = new ArrayList<Subnet>();

        for (String block : blocks) {
            subnets.add(parseSubnet(block));
        }
return subnets;
}

And of course parseSubnet is taken
from org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java

On a new connection, with logger set to DEBUG I get:

[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Firing a
SESSION_CREATED event for session 3
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Checking permissions
for: /XXX.XXX.XXX.XXX
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Denied
[ WARN] 2010-02-26 00:39:50,461 [] [217.148.186.136] Remote address not in
the whitelist; closing.
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Firing a CLOSE event
for session 3
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Firing a CLOSE event
for session 3
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Event CLOSE has been
fired for session 3
[DEBUG] 2010-02-26 00:39:50,461 [] [217.148.186.136] Event CLOSE has been
fired for session 3
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Event SESSION_CREATED
has been fired for session 3
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Firing a SESSION_OPENED
event for session 3
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Checking permissions
for: /XXX.XXX.XXX.XXX
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Denied
[ WARN] 2010-02-26 00:39:50,462 [] [217.148.186.136] Remote address not in
the whitelist; closing.
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Event SESSION_OPENED
has been fired for session 3
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Firing a SESSION_CLOSED
event for session 3
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Checking permissions
for: /XXX.XXX.XXX.XXX
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Denied
[ WARN] 2010-02-26 00:39:50,462 [] [217.148.186.136] Remote address not in
the whitelist; closing.
[DEBUG] 2010-02-26 00:39:50,462 [] [217.148.186.136] Event SESSION_CLOSED
has been fired for session 3

I don't understand why is checked 3 times, looks like is checked
on SESSION_CREATED, SESSION_OPENED and SESSION_CLOSED.

What do you think? Worth a merge?

Regards!
Index: core/src/main/java/org/apache/ftpserver/listener/Listener.java
===================================================================
--- core/src/main/java/org/apache/ftpserver/listener/Listener.java      
(revision 907785)
+++ core/src/main/java/org/apache/ftpserver/listener/Listener.java      
(working copy)
@@ -151,4 +151,20 @@
      * @return The list of {...@link Subnet}s
      */
     List<Subnet> getBlockedSubnets();
+    
+    /**
+     * Retrieves the {...@link InetAddress} for which this listener allows
+     * connections
+     * 
+     * @return The list of {...@link InetAddress}es
+     */
+    List<InetAddress> getAllowedAddresses();
+
+    /**
+     * Retrieves the {...@link Subnet}s for this listener allows connections
+     * 
+     * @return The list of {...@link Subnet}s
+     */
+    List<Subnet> getAllowedSubnets();
+
 }
\ No newline at end of file
Index: core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java
===================================================================
--- core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java       
(revision 907785)
+++ core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java       
(working copy)
@@ -54,6 +54,10 @@
     private List<InetAddress> blockedAddresses;
 
     private List<Subnet> blockedSubnets;
+    
+    private List<Subnet> allowedSubnets;
+    
+    private List<InetAddress> allowedAddresses;
 
     /**
      * Default constructor
@@ -75,6 +79,9 @@
         idleTimeout = listener.getIdleTimeout();
         blockedAddresses = listener.getBlockedAddresses();
         blockedSubnets = listener.getBlockedSubnets();
+        allowedAddresses = listener.getAllowedAddresses();
+        allowedSubnets = listener.getAllowedSubnets();
+
     }
 
     /**
@@ -89,7 +96,7 @@
        }
         return new NioListener(serverAddress, port, implicitSsl, ssl,
                 dataConnectionConfig, idleTimeout, blockedAddresses,
-                blockedSubnets);
+                blockedSubnets, allowedAddresses, allowedSubnets);
     }
 
     /**
@@ -234,6 +241,27 @@
     }
 
     /**
+     * Retrives the {...@link InetAddress} for which listeners created by this 
factory allows
+     * connections
+     * 
+     * @return The list of {...@link InetAddress}es
+     */
+    public List<InetAddress> getAllowedAddresses() {
+        return allowedAddresses;
+    }
+
+    /**
+     * Sets the {...@link InetAddress} that listeners created by this factory 
will allow from
+     * connecting
+     * 
+     * @param allowedAddresses
+     *            The list of {...@link InetAddress}es
+     */
+    public void setAllowedAddresses(List<InetAddress> allowedAddresses) {
+        this.allowedAddresses = allowedAddresses;
+    }
+
+    /**
      * Retrives the {...@link Subnet}s for which listeners created by this 
factory blocks connections
      * 
      * @return The list of {...@link Subnet}s
@@ -251,5 +279,24 @@
     public void setBlockedSubnets(List<Subnet> blockedSubnets) {
         this.blockedSubnets = blockedSubnets;
     }
+    /**
+     * Retrives the {...@link Subnet}s for which listeners created by this 
factory allows connections
+     * 
+     * @return The list of {...@link Subnet}s
+     */
+    public List<Subnet> getAllowedSubnets() {
+        return allowedSubnets;
+    }
+
+    /**
+     * Sets the {...@link Subnet}s that listeners created by this factory will 
allow from connecting
+     * @param blockedSubnets 
+     *  The list of {...@link Subnet}s
+     * @param blockedAddresses
+     */
+    public void setAllowedSubnets(List<Subnet> blockedSubnets) {
+        this.allowedSubnets = blockedSubnets;
+    }
 
+
 }
\ No newline at end of file
Index: 
core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java
===================================================================
--- core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java  
(revision 907785)
+++ core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java  
(working copy)
@@ -52,6 +52,10 @@
 
     private List<Subnet> blockedSubnets;
 
+    private List<InetAddress> allowedAddresses;
+
+    private List<Subnet> allowedSubnets;
+
     private DataConnectionConfiguration dataConnectionConfig;
 
     /**
@@ -59,7 +63,8 @@
      */
     public AbstractListener(String serverAddress, int port, boolean 
implicitSsl, 
             SslConfiguration sslConfiguration, DataConnectionConfiguration 
dataConnectionConfig,
-            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> 
blockedSubnets) {
+            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> 
blockedSubnets,
+            List<InetAddress> allowedAddresses, List<Subnet> allowedSubnets) {
         this.serverAddress = serverAddress;
         this.port = port;
         this.implicitSsl = implicitSsl;
@@ -73,6 +78,12 @@
         if(blockedSubnets != null) {
             this.blockedSubnets = Collections.unmodifiableList(blockedSubnets);
         }
+        if(allowedAddresses != null) {
+            this.allowedAddresses = 
Collections.unmodifiableList(allowedAddresses);
+        }
+        if(allowedSubnets != null) {
+            this.allowedSubnets = Collections.unmodifiableList(allowedSubnets);
+        }
         
     }
     
@@ -146,4 +157,23 @@
     public List<Subnet> getBlockedSubnets() {
         return blockedSubnets;
     }
+    /**
+     * Retrives the {...@link InetAddress} for which this listener allows
+     * connections
+     * 
+     * @return The list of {...@link InetAddress}es
+     */
+    public List<InetAddress> getAllowedAddresses() {
+        return allowedAddresses;
+    }
+
+    /**
+     * Retrieves the {...@link Subnet}s for this listener allows connections
+     * 
+     * @return The list of {...@link Subnet}s
+     */
+    public List<Subnet> getAllowedSubnets() {
+        return allowedSubnets;
+    }
+
 }
Index: core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java
===================================================================
--- core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java       
(revision 907785)
+++ core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java       
(working copy)
@@ -48,6 +48,7 @@
 import org.apache.mina.filter.executor.OrderedThreadPoolExecutor;
 import org.apache.mina.filter.firewall.BlacklistFilter;
 import org.apache.mina.filter.firewall.Subnet;
+import org.apache.mina.filter.firewall.WhitelistFilter;
 import org.apache.mina.filter.logging.MdcInjectionFilter;
 import org.apache.mina.filter.ssl.SslFilter;
 import org.apache.mina.transport.socket.SocketAcceptor;
@@ -86,17 +87,19 @@
             boolean implicitSsl,
             SslConfiguration sslConfiguration,
             DataConnectionConfiguration dataConnectionConfig, 
-            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> 
blockedSubnets) {
+            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> 
blockedSubnets,
+            List<InetAddress> allowedAddresses, List<Subnet> allowedSubnets) {
         super(serverAddress, port, implicitSsl, sslConfiguration, 
dataConnectionConfig, 
-                idleTimeout, blockedAddresses, blockedSubnets);   
+                idleTimeout, blockedAddresses, blockedSubnets, 
allowedAddresses, allowedSubnets);   
         
         updateBlacklistFilter();
+        updateWhitelistFilter();
     }
 
     private void updateBlacklistFilter() {
         if (acceptor != null) {
             BlacklistFilter filter = (BlacklistFilter) acceptor
-                    .getFilterChain().get("ipFilter");
+                    .getFilterChain().get("denyIpFilter");
 
             if (filter != null) {
                 if (getBlockedAddresses() != null) {
@@ -111,7 +114,27 @@
             }
         }
     }
+    
+    private void updateWhitelistFilter() {
+       if (acceptor != null) {
+            WhitelistFilter filter = (WhitelistFilter) acceptor
+                    .getFilterChain().get("allowIpFilter");
+
+            if (filter != null) {
+                if (getAllowedAddresses() != null) {
+                    filter.setWhitelist(getAllowedAddresses());
+                } else if (getAllowedSubnets() != null) {
+                    filter.setSubnetWhitelist(getAllowedSubnets());
+                } else {
+                    // an empty list clears the blocked addresses
+                       filter.setSubnetWhitelist(new ArrayList<Subnet>());
+                }
+
+            }
+        }
+    }
 
+
     /**
      * @see Listener#start(FtpServerContext)
      */
@@ -142,9 +165,11 @@
             acceptor.getFilterChain().addLast("mdcFilter", mdcFilter);
     
             // add and update the blacklist filter
-            acceptor.getFilterChain().addLast("ipFilter", new 
BlacklistFilter());
+            acceptor.getFilterChain().addLast("denyIpFilter", new 
BlacklistFilter());
             updateBlacklistFilter();
-    
+            // add and update the whitelist filter
+            acceptor.getFilterChain().addLast("allowIpFilter", new 
WhitelistFilter());
+            updateWhitelistFilter();
             acceptor.getFilterChain().addLast("threadPool",
                     new ExecutorFilter(filterExecutor));
             acceptor.getFilterChain().addLast("codec",
Index: core/src/main/java/org/apache/mina/filter/firewall/WhitelistFilter.java
===================================================================
--- core/src/main/java/org/apache/mina/filter/firewall/WhitelistFilter.java     
(revision 0)
+++ core/src/main/java/org/apache/mina/filter/firewall/WhitelistFilter.java     
(revision 0)
@@ -0,0 +1,250 @@
+/*
+ *  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.mina.filter.firewall;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.mina.core.filterchain.IoFilter;
+import org.apache.mina.core.filterchain.IoFilterAdapter;
+import org.apache.mina.core.session.IdleStatus;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.core.write.WriteRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {...@link IoFilter} which allows connections from whitelisted remote
+ * address.
+ *
+ * @author <a href="http://mina.apache.org";>Apache MINA Project</a>
+ * @org.apache.xbean.XBean
+ */
+public class WhitelistFilter extends IoFilterAdapter {
+    private final List<Subnet> whitelist = new CopyOnWriteArrayList<Subnet>();
+
+    private final static Logger LOGGER = 
LoggerFactory.getLogger(WhitelistFilter.class);
+    /**
+     * Sets the addresses to be whitelisted.
+     *
+     * NOTE: this call will remove any previously whitelisted addresses.
+     *
+     * @param addresses an array of addresses to be whitelisted.
+     */
+    public void setWhitelist(InetAddress[] addresses) {
+        if (addresses == null) {
+            throw new NullPointerException("addresses");
+        }
+        whitelist.clear();
+        for (int i = 0; i < addresses.length; i++) {
+            InetAddress addr = addresses[i];
+            allow(addr);
+        }
+    }
+
+    /**
+     * Sets the subnets to be whitelisted.
+     *
+     * NOTE: this call will remove any previously whitelisted subnets.
+     *
+     * @param subnets an array of subnets to be whitelisted.
+     */
+    public void setSubnetWhitelist(Subnet[] subnets) {
+        if (subnets == null) {
+            throw new NullPointerException("Subnets must not be null");
+        }
+        whitelist.clear();
+        for (Subnet subnet : subnets) {
+            allow(subnet);
+        }
+    }
+    
+    /**
+     * Sets the addresses to be whitelisted.
+     *
+     * NOTE: this call will remove any previously whitelisted addresses.
+     *
+     * @param addresses a collection of InetAddress objects representing the
+     *        addresses to be whitelisted.
+     * @throws IllegalArgumentException if the specified collections contains
+     *         no...@link InetAddress} objects.
+     */
+    public void setWhitelist(Iterable<InetAddress> addresses) {
+        if (addresses == null) {
+            throw new NullPointerException("addresses");
+        }
+
+        whitelist.clear();
+        
+        for( InetAddress address : addresses ){
+            allow(address);
+        }
+    }
+
+    /**
+     * Sets the subnets to be whitelisted.
+     *
+     * NOTE: this call will remove any previously whitelisted subnets.
+     *
+     * @param subnets an array of subnets to be whitelisted.
+     */
+    public void setSubnetWhitelist(Iterable<Subnet> subnets) {
+        if (subnets == null) {
+            throw new NullPointerException("Subnets must not be null");
+        }
+        whitelist.clear();
+        for (Subnet subnet : subnets) {
+            allow(subnet);
+        }
+    }
+
+    /**
+     * Allows the specified endpoint.
+     */
+    public void allow(InetAddress address) {
+        if (address == null) {
+            throw new NullPointerException("Adress to block can not be null");
+        }
+
+        allow(new Subnet(address, 32));
+    }
+
+    /**
+     * Allows the specified subnet.
+     */
+    public void allow(Subnet subnet) {
+        if(subnet == null) {
+            throw new NullPointerException("Subnet can not be null");
+        }
+        
+        whitelist.add(subnet);
+    }
+    
+    /**
+     * Unblocks the specified endpoint.
+     */
+    public void unallow(InetAddress address) {
+        if (address == null) {
+            throw new NullPointerException("Adress to unblock can not be 
null");
+        }
+        
+        unallow(new Subnet(address, 32));
+    }
+
+    /**
+     * Allows the specified subnet.
+     */
+    public void unallow(Subnet subnet) {
+        if (subnet == null) {
+            throw new NullPointerException("Subnet can not be null");
+        }
+        whitelist.remove(subnet);
+    }
+
+    @Override
+    public void sessionCreated(NextFilter nextFilter, IoSession session) {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.sessionCreated(session);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    @Override
+    public void sessionOpened(NextFilter nextFilter, IoSession session)
+            throws Exception {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.sessionOpened(session);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    @Override
+    public void sessionClosed(NextFilter nextFilter, IoSession session)
+            throws Exception {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.sessionClosed(session);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    @Override
+    public void sessionIdle(NextFilter nextFilter, IoSession session,
+            IdleStatus status) throws Exception {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.sessionIdle(session, status);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    @Override
+    public void messageReceived(NextFilter nextFilter, IoSession session,
+            Object message) {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.messageReceived(session, message);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    @Override
+    public void messageSent(NextFilter nextFilter, IoSession session,
+            WriteRequest writeRequest) throws Exception {
+        if (isAllowed(session)) {
+            // forward if not blocked
+            nextFilter.messageSent(session, writeRequest);
+        } else {
+            blockSession(session);
+        }
+    }
+
+    private void blockSession(IoSession session) {
+        LOGGER.warn("Remote address not in the whitelist; closing.");
+        session.close(true);
+    }
+
+    private boolean isAllowed(IoSession session) {
+        SocketAddress remoteAddress = session.getRemoteAddress();
+        if (remoteAddress instanceof InetSocketAddress) {
+            InetAddress address = ((InetSocketAddress) 
remoteAddress).getAddress();
+            LOGGER.debug("Checking whitelist: " + address);
+            // check all subnets
+            for(Subnet subnet : whitelist) {
+                if(subnet.inSubnet(address)) {
+                    LOGGER.debug("Allowed: " + address);
+                    return true;
+                }
+            }
+        }
+        LOGGER.debug("Denied");
+        return false;
+    }
+}

Reply via email to