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;
+ }
+}