Implemented FTPSERVER-357

git-svn-id: https://svn.apache.org/repos/asf/mina/ftpserver/trunk@928769 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java b/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java
index 060d18f..de22a80 100644
--- a/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java
+++ b/core/src/main/java/org/apache/ftpserver/config/spring/ListenerBeanDefinitionParser.java
@@ -1,318 +1,294 @@
-/*
- * 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.config.spring;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.ftpserver.DataConnectionConfiguration;
-import org.apache.ftpserver.DataConnectionConfigurationFactory;
-import org.apache.ftpserver.listener.ListenerFactory;
-import org.apache.ftpserver.ssl.SslConfiguration;
-import org.apache.ftpserver.ssl.SslConfigurationFactory;
-import org.apache.mina.filter.firewall.Subnet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.BeanDefinitionHolder;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
-import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.util.StringUtils;
-import org.w3c.dom.Element;
-
-/**
- * Parses the FtpServer "nio-listener" element into a Spring bean graph
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class ListenerBeanDefinitionParser extends
-        AbstractSingleBeanDefinitionParser {
-
-    private final Logger LOG = LoggerFactory
-            .getLogger(ListenerBeanDefinitionParser.class);
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected Class<?> getBeanClass(final Element element) {
-        return null;
-    }
-
-    /**
-     * Parse CIDR notations into MINA {@link Subnet}s. TODO: move to Mina
-     */
-    private Subnet parseSubnet(final String subnet) {
-        if (subnet == null) {
-            throw new NullPointerException("subnet can not be null");
-        }
-
-        String[] tokens = subnet.split("/");
-
-        String ipString;
-        String maskString;
-        if (tokens.length == 2) {
-            ipString = tokens[0];
-            maskString = tokens[1];
-        } else if (tokens.length == 1) {
-            ipString = tokens[0];
-            maskString = "32";
-        } else {
-            throw new IllegalArgumentException("Illegal subnet format: "
-                    + subnet);
-        }
-
-        InetAddress address;
-        try {
-            address = InetAddress.getByName(ipString);
-        } catch (UnknownHostException e) {
-            throw new IllegalArgumentException("Illegal IP address in subnet: "
-                    + subnet);
-        }
-
-        int mask = Integer.parseInt(maskString);
-        if (mask < 0 || mask > 32) {
-            throw new IllegalArgumentException("Mask must be in the range 0-32");
-        }
-
-        return new Subnet(address, mask);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void doParse(final Element element,
-            final ParserContext parserContext,
-            final BeanDefinitionBuilder builder) {
-
-        BeanDefinitionBuilder factoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ListenerFactory.class);
-
-        if (StringUtils.hasText(element.getAttribute("port"))) {
-            factoryBuilder.addPropertyValue("port", Integer.parseInt(element
-                    .getAttribute("port")));
-        }
-
-        SslConfiguration ssl = parseSsl(element);
-        if (ssl != null) {
-            factoryBuilder.addPropertyValue("sslConfiguration", ssl);
-        }
-
-        Element dataConElm = SpringUtil.getChildElement(element,
-                FtpServerNamespaceHandler.FTPSERVER_NS, "data-connection");
-        DataConnectionConfiguration dc = parseDataConnection(dataConElm, ssl);
-        factoryBuilder.addPropertyValue("dataConnectionConfiguration", dc);
-
-        if (StringUtils.hasText(element.getAttribute("idle-timeout"))) {
-            factoryBuilder.addPropertyValue("idleTimeout", SpringUtil.parseInt(
-                    element, "idle-timeout", 300));
-        }
-
-        String localAddress = SpringUtil.parseStringFromInetAddress(element,
-                "local-address");
-        if (localAddress != null) {
-            factoryBuilder.addPropertyValue("serverAddress", localAddress);
-        }
-        factoryBuilder.addPropertyValue("implicitSsl", SpringUtil.parseBoolean(
-                element, "implicit-ssl", false));
-
-        Element blacklistElm = SpringUtil.getChildElement(element,
-                FtpServerNamespaceHandler.FTPSERVER_NS, "blacklist");
-        if (blacklistElm != null
-                && StringUtils.hasText(blacklistElm.getTextContent())) {
-            String[] blocks = blacklistElm.getTextContent().split("[\\s,]+");
-            List<Subnet> subnets = new ArrayList<Subnet>();
-
-            for (String block : blocks) {
-                subnets.add(parseSubnet(block));
-            }
-
-            factoryBuilder.addPropertyValue("blockedSubnets", subnets);
-        }
-        
-        BeanDefinition factoryDefinition = factoryBuilder.getBeanDefinition();
-
-        String listenerFactoryName = parserContext.getReaderContext().generateBeanName(factoryDefinition);
-        
-        BeanDefinitionHolder factoryHolder = new BeanDefinitionHolder(factoryDefinition, listenerFactoryName);
-        registerBeanDefinition(factoryHolder, parserContext.getRegistry());
-
-        // set the factory on the listener bean
-        builder.getRawBeanDefinition().setFactoryBeanName(listenerFactoryName);
-        builder.getRawBeanDefinition().setFactoryMethodName("createListener");
-    }
-
-    private SslConfiguration parseSsl(final Element parent) {
-        Element sslElm = SpringUtil.getChildElement(parent,
-                FtpServerNamespaceHandler.FTPSERVER_NS, "ssl");
-
-        if (sslElm != null) {
-            SslConfigurationFactory ssl = new SslConfigurationFactory();
-
-            Element keyStoreElm = SpringUtil.getChildElement(sslElm,
-                    FtpServerNamespaceHandler.FTPSERVER_NS, "keystore");
-            if (keyStoreElm != null) {
-                ssl.setKeystoreFile(SpringUtil.parseFile(keyStoreElm, "file"));
-                ssl.setKeystorePassword(SpringUtil.parseString(keyStoreElm,
-                        "password"));
-
-                String type = SpringUtil.parseString(keyStoreElm, "type");
-                if (type != null) {
-                    ssl.setKeystoreType(type);
-                }
-
-                String keyAlias = SpringUtil.parseString(keyStoreElm,
-                        "key-alias");
-                if (keyAlias != null) {
-                    ssl.setKeyAlias(keyAlias);
-                }
-
-                String keyPassword = SpringUtil.parseString(keyStoreElm,
-                        "key-password");
-                if (keyPassword != null) {
-                    ssl.setKeyPassword(keyPassword);
-                }
-
-                String algorithm = SpringUtil.parseString(keyStoreElm,
-                        "algorithm");
-                if (algorithm != null) {
-                    ssl.setKeystoreAlgorithm(algorithm);
-                }
-            }
-
-            Element trustStoreElm = SpringUtil.getChildElement(sslElm,
-                    FtpServerNamespaceHandler.FTPSERVER_NS, "truststore");
-            if (trustStoreElm != null) {
-                ssl.setTruststoreFile(SpringUtil.parseFile(trustStoreElm,
-                        "file"));
-                ssl.setTruststorePassword(SpringUtil.parseString(trustStoreElm,
-                        "password"));
-
-                String type = SpringUtil.parseString(trustStoreElm, "type");
-                if (type != null) {
-                    ssl.setTruststoreType(type);
-                }
-
-                String algorithm = SpringUtil.parseString(trustStoreElm,
-                        "algorithm");
-                if (algorithm != null) {
-                    ssl.setTruststoreAlgorithm(algorithm);
-                }
-            }
-
-            String clientAuthStr = SpringUtil.parseString(sslElm,
-                    "client-authentication");
-            if (clientAuthStr != null) {
-                ssl.setClientAuthentication(clientAuthStr);
-            }
-
-            String enabledCiphersuites = SpringUtil.parseString(sslElm,
-                    "enabled-ciphersuites");
-            if (enabledCiphersuites != null) {
-                ssl.setEnabledCipherSuites(enabledCiphersuites.split(" "));
-            }
-
-            String protocol = SpringUtil.parseString(sslElm, "protocol");
-            if (protocol != null) {
-                ssl.setSslProtocol(protocol);
-            }
-
-            return ssl.createSslConfiguration();
-        } else {
-            return null;
-        }
-
-    }
-
-    private DataConnectionConfiguration parseDataConnection(
-            final Element element,
-            final SslConfiguration listenerSslConfiguration) {
-        DataConnectionConfigurationFactory dc = new DataConnectionConfigurationFactory();
-
-        if (element != null) {
-            
-            dc.setImplicitSsl(SpringUtil.parseBoolean(element, "implicit-ssl", false));
-            
-            // data con config element available
-            SslConfiguration ssl = parseSsl(element);
-
-            if (ssl != null) {
-                LOG.debug("SSL configuration found for the data connection");
-                dc.setSslConfiguration(ssl);
-            }
-
-            dc.setIdleTime(SpringUtil.parseInt(element, "idle-timeout", dc.getIdleTime()));
-
-            Element activeElm = SpringUtil.getChildElement(element,
-                    FtpServerNamespaceHandler.FTPSERVER_NS, "active");
-            if (activeElm != null) {
-                dc.setActiveEnabled(SpringUtil.parseBoolean(activeElm, "enabled",
-                        true));
-                dc.setActiveIpCheck(SpringUtil.parseBoolean(activeElm,
-                        "ip-check", false));
-                dc.setActiveLocalPort(SpringUtil.parseInt(activeElm,
-                        "local-port", 0));
-                
-                String localAddress = SpringUtil.parseStringFromInetAddress(
-                        activeElm, "local-address");
-                if (localAddress != null) {
-                	dc.setActiveLocalAddress(localAddress);
-                }
-            }
-
-            Element passiveElm = SpringUtil.getChildElement(element,
-                    FtpServerNamespaceHandler.FTPSERVER_NS, "passive");
-            if (passiveElm != null) {
-                String address = SpringUtil.parseStringFromInetAddress(passiveElm,
-                        "address");
-                if (address != null) {
-                	dc.setPassiveAddress(address);
-                }
-
-                String externalAddress = SpringUtil.parseStringFromInetAddress(
-                        passiveElm, "external-address");
-                if (externalAddress != null) {
-                    dc.setPassiveExternalAddress(externalAddress);
-                }
-
-                String ports = SpringUtil.parseString(passiveElm, "ports");
-                if (ports != null) {
-                    dc.setPassivePorts(ports);
-                }
-                dc.setPassiveIpCheck(SpringUtil.parseBoolean(passiveElm,
-                    "ip-check", false));
-            }
-        } else {
-            // no data conn config element, do we still have SSL config from the
-            // parent?
-            if (listenerSslConfiguration != null) {
-                LOG
-                        .debug("SSL configuration found for the listener, falling back for that for the data connection");
-                dc.setSslConfiguration(listenerSslConfiguration);
-            }
-        }
-
-        return dc.createDataConnectionConfiguration();
-    }
-
-}
+/*

+ * 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.config.spring;

+

+import java.net.InetAddress;

+import java.net.UnknownHostException;

+

+import org.apache.ftpserver.DataConnectionConfiguration;

+import org.apache.ftpserver.DataConnectionConfigurationFactory;

+import org.apache.ftpserver.FtpServerConfigurationException;

+import org.apache.ftpserver.ipfilter.DefaultIpFilter;

+import org.apache.ftpserver.ipfilter.IpFilterType;

+import org.apache.ftpserver.listener.ListenerFactory;

+import org.apache.ftpserver.ssl.SslConfiguration;

+import org.apache.ftpserver.ssl.SslConfigurationFactory;

+import org.apache.mina.filter.firewall.Subnet;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.config.BeanDefinition;

+import org.springframework.beans.factory.config.BeanDefinitionHolder;

+import org.springframework.beans.factory.support.BeanDefinitionBuilder;

+import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;

+import org.springframework.beans.factory.xml.ParserContext;

+import org.springframework.util.StringUtils;

+import org.w3c.dom.Element;

+

+/**

+ * Parses the FtpServer "nio-listener" element into a Spring bean graph

+ *

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ */

+public class ListenerBeanDefinitionParser extends

+        AbstractSingleBeanDefinitionParser {

+

+    private final Logger LOG = LoggerFactory

+            .getLogger(ListenerBeanDefinitionParser.class);

+

+    /**

+     * {@inheritDoc}

+     */

+    @Override

+    protected Class<?> getBeanClass(final Element element) {

+        return null;

+    }

+

+    /**

+     * {@inheritDoc}

+     */

+    @Override

+    protected void doParse(final Element element,

+            final ParserContext parserContext,

+            final BeanDefinitionBuilder builder) {

+

+        BeanDefinitionBuilder factoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(ListenerFactory.class);

+

+        if (StringUtils.hasText(element.getAttribute("port"))) {

+            factoryBuilder.addPropertyValue("port", Integer.parseInt(element

+                    .getAttribute("port")));

+        }

+

+        SslConfiguration ssl = parseSsl(element);

+        if (ssl != null) {

+            factoryBuilder.addPropertyValue("sslConfiguration", ssl);

+        }

+

+        Element dataConElm = SpringUtil.getChildElement(element,

+                FtpServerNamespaceHandler.FTPSERVER_NS, "data-connection");

+        DataConnectionConfiguration dc = parseDataConnection(dataConElm, ssl);

+        factoryBuilder.addPropertyValue("dataConnectionConfiguration", dc);

+

+        if (StringUtils.hasText(element.getAttribute("idle-timeout"))) {

+            factoryBuilder.addPropertyValue("idleTimeout", SpringUtil.parseInt(

+                    element, "idle-timeout", 300));

+        }

+

+        String localAddress = SpringUtil.parseStringFromInetAddress(element,

+                "local-address");

+        if (localAddress != null) {

+            factoryBuilder.addPropertyValue("serverAddress", localAddress);

+        }

+        factoryBuilder.addPropertyValue("implicitSsl", SpringUtil.parseBoolean(

+                element, "implicit-ssl", false));

+

+        Element blacklistElm = SpringUtil.getChildElement(element,

+                FtpServerNamespaceHandler.FTPSERVER_NS, "blacklist");

+        if (blacklistElm != null) {

+        	LOG.warn("Element 'blacklist' is deprecated, and may be removed in a future release. Please use 'ip-filter' instead. ");

+        	try {

+				DefaultIpFilter ipFilter = new DefaultIpFilter(IpFilterType.DENY, blacklistElm.getTextContent());

+	            factoryBuilder.addPropertyValue("ipFilter", ipFilter);

+			}

+			catch (UnknownHostException e) {

+				throw new IllegalArgumentException("Invalid IP address or subnet in the 'blacklist' element", e);

+			}

+        }

+        

+        Element ipFilterElement = SpringUtil.getChildElement(element, FtpServerNamespaceHandler.FTPSERVER_NS, "ip-filter");

+        if(ipFilterElement != null) {

+        	if(blacklistElm != null) {

+        		throw new FtpServerConfigurationException("Element 'ipFilter' may not be used when 'blacklist' element is specified. ");

+        	}

+        	String filterType = ipFilterElement.getAttribute("type");

+        	try {

+				DefaultIpFilter ipFilter = new DefaultIpFilter(IpFilterType.parse(filterType), ipFilterElement.getTextContent());

+	            factoryBuilder.addPropertyValue("ipFilter", ipFilter);

+			}

+			catch (UnknownHostException e) {

+				throw new IllegalArgumentException("Invalid IP address or subnet in the 'ip-filter' element");

+			}

+        }

+        

+        BeanDefinition factoryDefinition = factoryBuilder.getBeanDefinition();

+

+        String listenerFactoryName = parserContext.getReaderContext().generateBeanName(factoryDefinition);

+        

+        BeanDefinitionHolder factoryHolder = new BeanDefinitionHolder(factoryDefinition, listenerFactoryName);

+        registerBeanDefinition(factoryHolder, parserContext.getRegistry());

+

+        // set the factory on the listener bean

+        builder.getRawBeanDefinition().setFactoryBeanName(listenerFactoryName);

+        builder.getRawBeanDefinition().setFactoryMethodName("createListener");

+    }

+

+    private SslConfiguration parseSsl(final Element parent) {

+        Element sslElm = SpringUtil.getChildElement(parent,

+                FtpServerNamespaceHandler.FTPSERVER_NS, "ssl");

+

+        if (sslElm != null) {

+            SslConfigurationFactory ssl = new SslConfigurationFactory();

+

+            Element keyStoreElm = SpringUtil.getChildElement(sslElm,

+                    FtpServerNamespaceHandler.FTPSERVER_NS, "keystore");

+            if (keyStoreElm != null) {

+                ssl.setKeystoreFile(SpringUtil.parseFile(keyStoreElm, "file"));

+                ssl.setKeystorePassword(SpringUtil.parseString(keyStoreElm,

+                        "password"));

+

+                String type = SpringUtil.parseString(keyStoreElm, "type");

+                if (type != null) {

+                    ssl.setKeystoreType(type);

+                }

+

+                String keyAlias = SpringUtil.parseString(keyStoreElm,

+                        "key-alias");

+                if (keyAlias != null) {

+                    ssl.setKeyAlias(keyAlias);

+                }

+

+                String keyPassword = SpringUtil.parseString(keyStoreElm,

+                        "key-password");

+                if (keyPassword != null) {

+                    ssl.setKeyPassword(keyPassword);

+                }

+

+                String algorithm = SpringUtil.parseString(keyStoreElm,

+                        "algorithm");

+                if (algorithm != null) {

+                    ssl.setKeystoreAlgorithm(algorithm);

+                }

+            }

+

+            Element trustStoreElm = SpringUtil.getChildElement(sslElm,

+                    FtpServerNamespaceHandler.FTPSERVER_NS, "truststore");

+            if (trustStoreElm != null) {

+                ssl.setTruststoreFile(SpringUtil.parseFile(trustStoreElm,

+                        "file"));

+                ssl.setTruststorePassword(SpringUtil.parseString(trustStoreElm,

+                        "password"));

+

+                String type = SpringUtil.parseString(trustStoreElm, "type");

+                if (type != null) {

+                    ssl.setTruststoreType(type);

+                }

+

+                String algorithm = SpringUtil.parseString(trustStoreElm,

+                        "algorithm");

+                if (algorithm != null) {

+                    ssl.setTruststoreAlgorithm(algorithm);

+                }

+            }

+

+            String clientAuthStr = SpringUtil.parseString(sslElm,

+                    "client-authentication");

+            if (clientAuthStr != null) {

+                ssl.setClientAuthentication(clientAuthStr);

+            }

+

+            String enabledCiphersuites = SpringUtil.parseString(sslElm,

+                    "enabled-ciphersuites");

+            if (enabledCiphersuites != null) {

+                ssl.setEnabledCipherSuites(enabledCiphersuites.split(" "));

+            }

+

+            String protocol = SpringUtil.parseString(sslElm, "protocol");

+            if (protocol != null) {

+                ssl.setSslProtocol(protocol);

+            }

+

+            return ssl.createSslConfiguration();

+        } else {

+            return null;

+        }

+

+    }

+

+    private DataConnectionConfiguration parseDataConnection(

+            final Element element,

+            final SslConfiguration listenerSslConfiguration) {

+        DataConnectionConfigurationFactory dc = new DataConnectionConfigurationFactory();

+

+        if (element != null) {

+            

+            dc.setImplicitSsl(SpringUtil.parseBoolean(element, "implicit-ssl", false));

+            

+            // data con config element available

+            SslConfiguration ssl = parseSsl(element);

+

+            if (ssl != null) {

+                LOG.debug("SSL configuration found for the data connection");

+                dc.setSslConfiguration(ssl);

+            }

+

+            dc.setIdleTime(SpringUtil.parseInt(element, "idle-timeout", dc.getIdleTime()));

+

+            Element activeElm = SpringUtil.getChildElement(element,

+                    FtpServerNamespaceHandler.FTPSERVER_NS, "active");

+            if (activeElm != null) {

+                dc.setActiveEnabled(SpringUtil.parseBoolean(activeElm, "enabled",

+                        true));

+                dc.setActiveIpCheck(SpringUtil.parseBoolean(activeElm,

+                        "ip-check", false));

+                dc.setActiveLocalPort(SpringUtil.parseInt(activeElm,

+                        "local-port", 0));

+                

+                String localAddress = SpringUtil.parseStringFromInetAddress(

+                        activeElm, "local-address");

+                if (localAddress != null) {

+                	dc.setActiveLocalAddress(localAddress);

+                }

+            }

+

+            Element passiveElm = SpringUtil.getChildElement(element,

+                    FtpServerNamespaceHandler.FTPSERVER_NS, "passive");

+            if (passiveElm != null) {

+                String address = SpringUtil.parseStringFromInetAddress(passiveElm,

+                        "address");

+                if (address != null) {

+                	dc.setPassiveAddress(address);

+                }

+

+                String externalAddress = SpringUtil.parseStringFromInetAddress(

+                        passiveElm, "external-address");

+                if (externalAddress != null) {

+                    dc.setPassiveExternalAddress(externalAddress);

+                }

+

+                String ports = SpringUtil.parseString(passiveElm, "ports");

+                if (ports != null) {

+                    dc.setPassivePorts(ports);

+                }

+                dc.setPassiveIpCheck(SpringUtil.parseBoolean(passiveElm,

+                    "ip-check", false));

+            }

+        } else {

+            // no data conn config element, do we still have SSL config from the

+            // parent?

+            if (listenerSslConfiguration != null) {

+                LOG

+                        .debug("SSL configuration found for the listener, falling back for that for the data connection");

+                dc.setSslConfiguration(listenerSslConfiguration);

+            }

+        }

+

+        return dc.createDataConnectionConfiguration();

+    }

+

+}

diff --git a/core/src/main/java/org/apache/ftpserver/ipfilter/DefaultIpFilter.java b/core/src/main/java/org/apache/ftpserver/ipfilter/DefaultIpFilter.java
new file mode 100644
index 0000000..0eb5528
--- /dev/null
+++ b/core/src/main/java/org/apache/ftpserver/ipfilter/DefaultIpFilter.java
@@ -0,0 +1,217 @@
+/*

+ * 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.ipfilter;

+

+import java.net.InetAddress;

+import java.net.UnknownHostException;

+import java.util.Collection;

+import java.util.HashSet;

+import java.util.concurrent.CopyOnWriteArraySet;

+

+import org.apache.mina.filter.firewall.Subnet;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+/**

+ * Default implementation of the <code>IpFilter</code> interface, which uses

+ * specific IP addresses or ranges of IP addresses that can be blocked or

+ * allowed.

+ * 

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ * 

+ */

+

+public class DefaultIpFilter extends CopyOnWriteArraySet<Subnet> implements

+	IpFilter {

+

+	/**

+	 * Logger

+	 */

+	Logger LOGGER = LoggerFactory.getLogger(DefaultIpFilter.class);

+

+	/**

+	 * Serial version UID

+	 */

+	private static final long serialVersionUID = 4887092372700628783L;

+

+	/**

+	 * filter type

+	 */

+	private IpFilterType type = null;

+

+	/**

+	 * Creates a new instance of <code>DefaultIpFilter</code>.

+	 * 

+	 * @param type

+	 *            the filter type

+	 */

+	public DefaultIpFilter(IpFilterType type) {

+		this(type, new HashSet<Subnet>(0));

+	}

+

+	/**

+	 * Creates a new instance of <code>DefaultIpFilter</code>.

+	 * 

+	 * @param type

+	 *            the filter type

+	 * @param collection

+	 *            a collection of <code>Subnet</code>s to filter out/in.

+	 */

+	public DefaultIpFilter(IpFilterType type,

+		Collection<? extends Subnet> collection) {

+		super(collection);

+		this.type = type;

+	}

+

+	/**

+	 * Creates a new instance of <code>DefaultIpFilter</code>.

+	 * 

+	 * @param type

+	 *            the filter type

+	 * @param addresses

+	 *            a comma, space, tab, LF separated list of IP addresses/CIDRs.

+	 * @throws UnknownHostException

+	 *             propagated

+	 * @throws NumberFormatException

+	 *             propagated

+	 */

+	public DefaultIpFilter(IpFilterType type, String addresses)

+		throws NumberFormatException, UnknownHostException {

+		super();

+		this.type = type;

+		if (addresses != null) {

+			String[] tokens = addresses.split("[\\s,]+");

+			for (String token : tokens) {

+				if (token.trim().length() > 0) {

+					add(token);

+				}

+			}

+		}

+		if (LOGGER.isDebugEnabled()) {

+			LOGGER.debug(

+				"Created DefaultIpFilter of type {} with the subnets {}", type,

+				this);

+		}

+	}

+

+	/**

+	 * Returns the type of this filter.

+	 * 

+	 * @return the type of this filter.

+	 */

+	public IpFilterType getType() {

+		return type;

+	}

+

+	/**

+	 * Sets the type of this filter.

+	 * 

+	 * @param type

+	 *            the type of this filter.

+	 */

+	// TODO should we allow changing the filter type once it is created? I don't

+	// think we should.

+	public void setType(IpFilterType type) {

+		this.type = type;

+	}

+

+	/**

+	 * Adds the given string representation of InetAddress or CIDR notation to

+	 * this filter.

+	 * 

+	 * @param str

+	 *            the string representation of InetAddress or CIDR notation

+	 * @return if the given element was added or not. <code>true</code>, if the

+	 *         given element was added to the filter; <code>false</code>, if the

+	 *         element already exists in the filter.

+	 * @throws NumberFormatException

+	 *             propagated

+	 * @throws UnknownHostException

+	 *             propagated

+	 */

+	public boolean add(String str) throws NumberFormatException,

+		UnknownHostException {

+		// This is required so we do not block loopback address if some one adds

+		// a string with blanks as the InetAddress class assumes loopback

+		// address on blank string.

+		if (str.trim().length() < 1) {

+			throw new IllegalArgumentException("Invalid IP Address or Subnet: "

+				+ str);

+		}

+		String[] tokens = str.split("/");

+		if (tokens.length == 2) {

+			return add(new Subnet(InetAddress.getByName(tokens[0]),

+				Integer.parseInt(tokens[1])));

+		}

+		else {

+			return add(new Subnet(InetAddress.getByName(tokens[0]), 32));

+		}

+	}

+

+	public boolean accept(InetAddress address) {

+		switch (type) {

+			case ALLOW:

+				for (Subnet subnet : this) {

+					if (subnet.inSubnet(address)) {

+						if (LOGGER.isDebugEnabled()) {

+							LOGGER.debug(

+								"Allowing connection from {} because it matches with the whitelist subnet {}",

+								new Object[] { address, subnet });

+						}

+						return true;

+					}

+				}

+				if (LOGGER.isDebugEnabled()) {

+					LOGGER.debug(

+						"Denying connection from {} because it does not match any of the whitelist subnets",

+						new Object[] { address });

+				}

+				return false;

+			case DENY:

+				if (isEmpty()) {

+					if (LOGGER.isDebugEnabled()) {

+						LOGGER.debug(

+							"Allowing connection from {} because blacklist is empty",

+							new Object[] { address });

+					}

+					return true;

+				}

+				for (Subnet subnet : this) {

+					if (subnet.inSubnet(address)) {

+						if (LOGGER.isDebugEnabled()) {

+							LOGGER.debug(

+								"Denying connection from {} because it matches with the blacklist subnet {}",

+								new Object[] { address, subnet });

+						}

+						return false;

+					}

+				}

+				if (LOGGER.isDebugEnabled()) {

+					LOGGER.debug(

+						"Allowing connection from {} because it does not match any of the blacklist subnets",

+						new Object[] { address });

+				}

+				return true;

+			default:

+				throw new RuntimeException(

+					"Unknown or unimplemented filter type: " + type);

+		}

+	}

+}

diff --git a/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilter.java b/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilter.java
new file mode 100644
index 0000000..53b2b53
--- /dev/null
+++ b/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilter.java
@@ -0,0 +1,43 @@
+/*

+ * 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.ipfilter;

+

+import java.net.InetAddress;

+

+/**

+ * The interface for filtering connections based on the client's IP address.

+ * 

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ * 

+ */

+

+public interface IpFilter {

+

+	/**

+	 * Tells whether or not the given IP address is accepted by this filter.

+	 * 

+	 * @param address

+	 *            the IP address to check

+	 * @return <code>true</code>, if the given IP address is accepted by this

+	 *         filter; <code>false</code>, otherwise.

+	 */

+	public boolean accept(InetAddress address);

+

+}

diff --git a/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilterType.java b/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilterType.java
new file mode 100644
index 0000000..9538d2e
--- /dev/null
+++ b/core/src/main/java/org/apache/ftpserver/ipfilter/IpFilterType.java
@@ -0,0 +1,58 @@
+/*

+ * 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.ipfilter;

+

+/**

+ * Defines various types of IP Filters.

+ * 

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ * 

+ */

+public enum IpFilterType {

+

+	/**

+	 * filter type that allows a set of predefined IP addresses, also known as a

+	 * white list.

+	 */

+	ALLOW,

+

+	/**

+	 * filter type that blocks a set of predefined IP addresses, also known as a

+	 * black list.

+	 */

+	DENY;

+

+	/**

+	 * Parses the given string into its equivalent enum.

+	 * 

+	 * @param value

+	 *            the string value to parse.

+	 * @return the equivalent enum

+	 */

+	public static IpFilterType parse(String value) {

+		for (IpFilterType type : values()) {

+			if (type.name().equalsIgnoreCase(value)) {

+				return type;

+			}

+		}

+		throw new IllegalArgumentException("Invalid IpFilterType: " + value);

+	}

+

+}

diff --git a/core/src/main/java/org/apache/ftpserver/ipfilter/MinaIpFilter.java b/core/src/main/java/org/apache/ftpserver/ipfilter/MinaIpFilter.java
new file mode 100644
index 0000000..77451cc
--- /dev/null
+++ b/core/src/main/java/org/apache/ftpserver/ipfilter/MinaIpFilter.java
@@ -0,0 +1,69 @@
+/*

+ * 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.ipfilter;

+

+import java.net.InetAddress;

+import java.net.InetSocketAddress;

+import java.net.SocketAddress;

+

+import org.apache.mina.core.filterchain.IoFilterAdapter;

+import org.apache.mina.core.session.IoSession;

+

+/**

+ * An implementation of Mina Filter to filter clients based on the originating

+ * IP address.

+ * 

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ * 

+ */

+

+public class MinaIpFilter extends IoFilterAdapter {

+

+	/**

+	 * The actual <code>IpFilter</code> used by this filter.

+	 */

+	private IpFilter filter = null;

+

+	/**

+	 * Creates a new instance of <code>MinaIpFilter</code>.

+	 * 

+	 * @param filter

+	 *            the filter

+	 */

+	public MinaIpFilter(IpFilter filter) {

+		this.filter = filter;

+	}

+

+	@Override

+	public void sessionCreated(NextFilter nextFilter, IoSession session) {

+		SocketAddress remoteAddress = session.getRemoteAddress();

+		if (remoteAddress instanceof InetSocketAddress) {

+			InetAddress ipAddress = ((InetSocketAddress) remoteAddress).getAddress();

+			// TODO we probably have to check if the InetAddress is a version 4

+			// address, or else, the result would probably be unknown.

+			if (!filter.accept(ipAddress)) {

+				session.close(true);

+			}

+			else {

+				nextFilter.sessionCreated(session);

+			}

+		}

+	}

+}

diff --git a/core/src/main/java/org/apache/ftpserver/listener/Listener.java b/core/src/main/java/org/apache/ftpserver/listener/Listener.java
index 05bac9c..ce9d90c 100644
--- a/core/src/main/java/org/apache/ftpserver/listener/Listener.java
+++ b/core/src/main/java/org/apache/ftpserver/listener/Listener.java
@@ -26,6 +26,7 @@
 import org.apache.ftpserver.DataConnectionConfiguration;
 import org.apache.ftpserver.impl.FtpIoSession;
 import org.apache.ftpserver.impl.FtpServerContext;
+import org.apache.ftpserver.ipfilter.IpFilter;
 import org.apache.ftpserver.ssl.SslConfiguration;
 import org.apache.mina.filter.firewall.Subnet;
 
@@ -138,17 +139,36 @@
     int getIdleTimeout();
 
     /**
-     * Retrieves the {@link InetAddress} for which this listener blocks
-     * connections
-     * 
-     * @return The list of {@link InetAddress}es
-     */
-    List<InetAddress> getBlockedAddresses();
+	 * @deprecated Replaced by IpFilter. Retrieves the {@link InetAddress} for
+	 *             which this listener blocks connections.
+	 * 
+	 * @return The list of {@link InetAddress}es. This method returns a valid
+	 *         list if and only if there is an <code>IpFilter</code> set, and,
+	 *         if it is an instance of <code>DefaultIpFilter</code> and it is of
+	 *         type <code>IpFilterType.DENY</code>. This functionality is
+	 *         provided for backward compatibility purpose only.
+	 */
+	@Deprecated
+	List<InetAddress> getBlockedAddresses();
 
     /**
-     * Retrieves the {@link Subnet}s for this listener blocks connections
+     * @deprecated Replaced by IpFilter. 
+     * Retrieves the {@link Subnet}s for this listener blocks connections. 
      * 
-     * @return The list of {@link Subnet}s
+     * @return The list of {@link Subnet}s. This method returns a valid
+	 *         list if and only if there is an <code>IpFilter</code> set, and,
+	 *         if it is an instance of <code>DefaultIpFilter</code> and it is of
+	 *         type <code>IpFilterType.DENY</code>. This functionality is
+	 *         provided for backward compatibility purpose only.
      */
     List<Subnet> getBlockedSubnets();
+    
+    /**
+	 * Returns the IP filter associated with this listener. May return
+	 * <code>null</code>.
+	 * 
+	 * @return the IP filter associated with this listener. May return
+	 *         <code>null</code>.
+	 */
+	IpFilter getIpFilter();
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java b/core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java
index d98120c..1a3532b 100644
--- a/core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java
+++ b/core/src/main/java/org/apache/ftpserver/listener/ListenerFactory.java
@@ -1,255 +1,301 @@
-/*
- * 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;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.List;
-
-import org.apache.ftpserver.DataConnectionConfiguration;
-import org.apache.ftpserver.DataConnectionConfigurationFactory;
-import org.apache.ftpserver.FtpServerConfigurationException;
-import org.apache.ftpserver.listener.nio.NioListener;
-import org.apache.ftpserver.ssl.SslConfiguration;
-import org.apache.mina.filter.firewall.Subnet;
-
-/**
- * Factory for listeners. Listeners themselves are immutable and must be 
- * created using this factory.
- *
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public class ListenerFactory {
-
-    private String serverAddress;
-
-    private int port = 21;
-
-    private SslConfiguration ssl;
-
-    private boolean implicitSsl = false;
-
-    private DataConnectionConfiguration dataConnectionConfig = new DataConnectionConfigurationFactory()
-            .createDataConnectionConfiguration();
-
-    private int idleTimeout = 300;
-
-    private List<InetAddress> blockedAddresses;
-
-    private List<Subnet> blockedSubnets;
-
-    /**
-     * Default constructor
-     */
-    public ListenerFactory() {
-        // do nothing
-    }
-
-    /**
-     * Copy constructor, will copy properties from the provided listener.
-     * @param listener The listener which properties will be used for this factory
-     */
-    public ListenerFactory(Listener listener) {
-        serverAddress = listener.getServerAddress();
-        port = listener.getPort();
-        ssl = listener.getSslConfiguration();
-        implicitSsl = listener.isImplicitSsl();
-        dataConnectionConfig = listener.getDataConnectionConfiguration();
-        idleTimeout = listener.getIdleTimeout();
-        blockedAddresses = listener.getBlockedAddresses();
-        blockedSubnets = listener.getBlockedSubnets();
-    }
-
-    /**
-     * Create a listener based on the settings of this factory. The listener is immutable.
-     * @return The created listener
-     */
-    public Listener createListener() {
-    	try{
-    		InetAddress.getByName(serverAddress);
-    	}catch(UnknownHostException e){
-    		throw new FtpServerConfigurationException("Unknown host",e);
-    	}
-        return new NioListener(serverAddress, port, implicitSsl, ssl,
-                dataConnectionConfig, idleTimeout, blockedAddresses,
-                blockedSubnets);
-    }
-
-    /**
-     * Is listeners created by this factory in SSL mode automatically or must the client explicitly
-     * request to use SSL
-     * 
-     * @return true is listeners created by this factory is automatically in SSL mode, false
-     *         otherwise
-     */
-    public boolean isImplicitSsl() {
-        return implicitSsl;
-    }
-
-    /**
-     * Should listeners created by this factory be in SSL mode automatically or must the client
-     * explicitly request to use SSL
-     * 
-     * @param implicitSsl
-     *            true is listeners created by this factory should automatically be in SSL mode,
-     *            false otherwise
-     */
-    public void setImplicitSsl(boolean implicitSsl) {
-        this.implicitSsl = implicitSsl;
-    }
-
-    /**
-     * Get the port on which listeners created by this factory is waiting for requests. 
-     * 
-     * @return The port
-     */
-    public int getPort() {
-        return port;
-    }
-
-    /**
-     * Set the port on which listeners created by this factory will accept requests. Or set to 0
-     * (zero) is the port should be automatically assigned
-     * 
-     * @param port
-     *            The port to use.
-     */
-    public void setPort(int port) {
-        this.port = port;
-    }
-
-    /**
-     * Get the {@link InetAddress} used for binding the local socket. Defaults
-     * to null, that is, the server binds to all available network interfaces
-     * 
-     * @return The local socket {@link InetAddress}, if set
-     */
-    public String getServerAddress()  {
-        return serverAddress;
-    }
-
-    /**
-     * Set the {@link InetAddress} used for binding the local socket. Defaults
-     * to null, that is, the server binds to all available network interfaces
-     * 
-     * @param serverAddress
-     *            The local socket {@link InetAddress}
-     */
-    public void setServerAddress(String serverAddress) {
-        this.serverAddress = serverAddress;
-    }
-
-    /**
-     * Get the {@link SslConfiguration} used for listeners created by this factory
-     * 
-     * @return The {@link SslConfiguration}
-     */
-    public SslConfiguration getSslConfiguration() {
-        return ssl;
-    }
-
-    /**
-     * Set the {@link SslConfiguration} to use by listeners created by this factory
-     * @param ssl The {@link SslConfiguration}
-     */
-    public void setSslConfiguration(SslConfiguration ssl) {
-        this.ssl = ssl;
-    }
-
-    /**
-     * Get configuration for data connections made within listeners created by this factory
-     * 
-     * @return The data connection configuration
-     */
-    public DataConnectionConfiguration getDataConnectionConfiguration() {
-        return dataConnectionConfig;
-    }
-
-    /**
-     * Set configuration for data connections made within listeners created by this factory
-     * 
-     * @param dataConnectionConfig
-     *            The data connection configuration
-     */
-    public void setDataConnectionConfiguration(
-            DataConnectionConfiguration dataConnectionConfig) {
-        this.dataConnectionConfig = dataConnectionConfig;
-    }
-
-    /**
-     * Get the number of seconds during which no network activity 
-     * is allowed before a session is closed due to inactivity.  
-     * @return The idle time out
-     */
-    public int getIdleTimeout() {
-        return idleTimeout;
-    }
-
-    /**
-     * Set the number of seconds during which no network activity 
-     * is allowed before a session is closed due to inactivity.  
-     *
-     * @param idleTimeout The idle timeout in seconds
-     */
-    public void setIdleTimeout(int idleTimeout) {
-        this.idleTimeout = idleTimeout;
-    }
-
-    /**
-     * Retrives the {@link InetAddress} for which listeners created by this factory blocks
-     * connections
-     * 
-     * @return The list of {@link InetAddress}es
-     */
-    public List<InetAddress> getBlockedAddresses() {
-        return blockedAddresses;
-    }
-
-    /**
-     * Sets the {@link InetAddress} that listeners created by this factory will block from
-     * connecting
-     * 
-     * @param blockedAddresses
-     *            The list of {@link InetAddress}es
-     */
-    public void setBlockedAddresses(List<InetAddress> blockedAddresses) {
-        this.blockedAddresses = blockedAddresses;
-    }
-
-    /**
-     * Retrives the {@link Subnet}s for which listeners created by this factory blocks connections
-     * 
-     * @return The list of {@link Subnet}s
-     */
-    public List<Subnet> getBlockedSubnets() {
-        return blockedSubnets;
-    }
-
-    /**
-     * Sets the {@link Subnet}s that listeners created by this factory will block from connecting
-     * @param blockedSubnets 
-     *  The list of {@link Subnet}s
-     * @param blockedAddresses
-     */
-    public void setBlockedSubnets(List<Subnet> blockedSubnets) {
-        this.blockedSubnets = blockedSubnets;
-    }
-
+/*

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

+

+import java.net.InetAddress;

+import java.net.UnknownHostException;

+import java.util.List;

+

+import org.apache.ftpserver.DataConnectionConfiguration;

+import org.apache.ftpserver.DataConnectionConfigurationFactory;

+import org.apache.ftpserver.FtpServerConfigurationException;

+import org.apache.ftpserver.ipfilter.IpFilter;

+import org.apache.ftpserver.listener.nio.NioListener;

+import org.apache.ftpserver.ssl.SslConfiguration;

+import org.apache.mina.filter.firewall.Subnet;

+

+/**

+ * Factory for listeners. Listeners themselves are immutable and must be 

+ * created using this factory.

+ *

+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>

+ */

+public class ListenerFactory {

+

+    private String serverAddress;

+

+    private int port = 21;

+

+    private SslConfiguration ssl;

+

+    private boolean implicitSsl = false;

+

+    private DataConnectionConfiguration dataConnectionConfig = new DataConnectionConfigurationFactory()

+            .createDataConnectionConfiguration();

+

+    private int idleTimeout = 300;

+

+    private List<InetAddress> blockedAddresses;

+

+    private List<Subnet> blockedSubnets;

+    

+    /**

+     * The IP filter

+     */

+    private IpFilter ipFilter = null;

+

+    /**

+     * Default constructor

+     */

+    public ListenerFactory() {

+        // do nothing

+    }

+

+    /**

+     * Copy constructor, will copy properties from the provided listener.

+     * @param listener The listener which properties will be used for this factory

+     */

+    public ListenerFactory(Listener listener) {

+        serverAddress = listener.getServerAddress();

+        port = listener.getPort();

+        ssl = listener.getSslConfiguration();

+        implicitSsl = listener.isImplicitSsl();

+        dataConnectionConfig = listener.getDataConnectionConfiguration();

+        idleTimeout = listener.getIdleTimeout();

+        //TODO remove the next two lines if and when we remove the deprecated methods. 

+        blockedAddresses = listener.getBlockedAddresses();

+        blockedSubnets = listener.getBlockedSubnets();

+        this.ipFilter = listener.getIpFilter();

+    }

+

+    /**

+     * Create a listener based on the settings of this factory. The listener is immutable.

+     * @return The created listener

+     */

+    public Listener createListener() {

+    	try{

+    		InetAddress.getByName(serverAddress);

+    	}catch(UnknownHostException e){

+    		throw new FtpServerConfigurationException("Unknown host",e);

+    	}

+    	//Deal with the old style black list and new IP Filter here. 

+    	if(ipFilter != null) {

+    		 if(blockedAddresses != null || blockedSubnets != null) {

+    			 throw new IllegalStateException("Usage of IPFilter in combination with blockedAddesses/subnets is not supported. ");

+    		 }

+    	}

+    	if(blockedAddresses != null || blockedSubnets != null) {

+            return new NioListener(serverAddress, port, implicitSsl, ssl,

+                dataConnectionConfig, idleTimeout, blockedAddresses, blockedSubnets);

+    	}

+    	else {

+	        return new NioListener(serverAddress, port, implicitSsl, ssl,

+	        	dataConnectionConfig, idleTimeout, ipFilter);

+    	}

+    }

+

+    /**

+     * Is listeners created by this factory in SSL mode automatically or must the client explicitly

+     * request to use SSL

+     * 

+     * @return true is listeners created by this factory is automatically in SSL mode, false

+     *         otherwise

+     */

+    public boolean isImplicitSsl() {

+        return implicitSsl;

+    }

+

+    /**

+     * Should listeners created by this factory be in SSL mode automatically or must the client

+     * explicitly request to use SSL

+     * 

+     * @param implicitSsl

+     *            true is listeners created by this factory should automatically be in SSL mode,

+     *            false otherwise

+     */

+    public void setImplicitSsl(boolean implicitSsl) {

+        this.implicitSsl = implicitSsl;

+    }

+

+    /**

+     * Get the port on which listeners created by this factory is waiting for requests. 

+     * 

+     * @return The port

+     */

+    public int getPort() {

+        return port;

+    }

+

+    /**

+     * Set the port on which listeners created by this factory will accept requests. Or set to 0

+     * (zero) is the port should be automatically assigned

+     * 

+     * @param port

+     *            The port to use.

+     */

+    public void setPort(int port) {

+        this.port = port;

+    }

+

+    /**

+     * Get the {@link InetAddress} used for binding the local socket. Defaults

+     * to null, that is, the server binds to all available network interfaces

+     * 

+     * @return The local socket {@link InetAddress}, if set

+     */

+    public String getServerAddress()  {

+        return serverAddress;

+    }

+

+    /**

+     * Set the {@link InetAddress} used for binding the local socket. Defaults

+     * to null, that is, the server binds to all available network interfaces

+     * 

+     * @param serverAddress

+     *            The local socket {@link InetAddress}

+     */

+    public void setServerAddress(String serverAddress) {

+        this.serverAddress = serverAddress;

+    }

+

+    /**

+     * Get the {@link SslConfiguration} used for listeners created by this factory

+     * 

+     * @return The {@link SslConfiguration}

+     */

+    public SslConfiguration getSslConfiguration() {

+        return ssl;

+    }

+

+    /**

+     * Set the {@link SslConfiguration} to use by listeners created by this factory

+     * @param ssl The {@link SslConfiguration}

+     */

+    public void setSslConfiguration(SslConfiguration ssl) {

+        this.ssl = ssl;

+    }

+

+    /**

+     * Get configuration for data connections made within listeners created by this factory

+     * 

+     * @return The data connection configuration

+     */

+    public DataConnectionConfiguration getDataConnectionConfiguration() {

+        return dataConnectionConfig;

+    }

+

+    /**

+     * Set configuration for data connections made within listeners created by this factory

+     * 

+     * @param dataConnectionConfig

+     *            The data connection configuration

+     */

+    public void setDataConnectionConfiguration(

+            DataConnectionConfiguration dataConnectionConfig) {

+        this.dataConnectionConfig = dataConnectionConfig;

+    }

+

+    /**

+     * Get the number of seconds during which no network activity 

+     * is allowed before a session is closed due to inactivity.  

+     * @return The idle time out

+     */

+    public int getIdleTimeout() {

+        return idleTimeout;

+    }

+

+    /**

+     * Set the number of seconds during which no network activity 

+     * is allowed before a session is closed due to inactivity.  

+     *

+     * @param idleTimeout The idle timeout in seconds

+     */

+    public void setIdleTimeout(int idleTimeout) {

+        this.idleTimeout = idleTimeout;

+    }

+

+    /**

+     * @deprecated Replaced by the IpFilter.    

+     * Retrieves the {@link InetAddress} for which listeners created by this factory blocks

+     * connections

+     * 

+     * @return The list of {@link InetAddress}es

+     */

+    @Deprecated

+    public List<InetAddress> getBlockedAddresses() {

+        return blockedAddresses;

+    }

+

+    /**

+     * @deprecated Replaced by the IpFilter.    

+     * Sets the {@link InetAddress} that listeners created by this factory will block from

+     * connecting

+     * 

+     * @param blockedAddresses

+     *            The list of {@link InetAddress}es

+     */

+    @Deprecated

+    public void setBlockedAddresses(List<InetAddress> blockedAddresses) {

+        this.blockedAddresses = blockedAddresses;

+    }

+

+    /**

+     * @deprecated Replaced by the IpFilter.    

+     * Retrives the {@link Subnet}s for which listeners created by this factory blocks connections

+     * 

+     * @return The list of {@link Subnet}s

+     */

+    @Deprecated

+    public List<Subnet> getBlockedSubnets() {

+        return blockedSubnets;

+    }

+

+    /**

+     * @deprecated Replaced by the IpFilter.    

+     * Sets the {@link Subnet}s that listeners created by this factory will block from connecting

+     * @param blockedSubnets 

+     *  The list of {@link Subnet}s

+     * @param blockedAddresses

+     */

+    @Deprecated

+    public void setBlockedSubnets(List<Subnet> blockedSubnets) {

+        this.blockedSubnets = blockedSubnets;

+    }

+    

+    /**

+	 * Returns the currently configured IP filter, if any.

+	 * 

+	 * @return the currently configured IP filter, if any. Returns

+	 *         <code>null</code>, if no IP filter is configured.

+	 */

+	public IpFilter getIpFilter() {

+		return ipFilter;

+	}

+

+	/**

+	 * Sets the IP filter to the given filter.

+	 * 

+	 * @param ipFilter

+	 *            the IP filter.

+	 */

+	public void setIpFilter(IpFilter ipFilter) {

+		this.ipFilter = ipFilter;

+	}

 }
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java b/core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java
index 4a5b8a3..f00d812 100644
--- a/core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java
+++ b/core/src/main/java/org/apache/ftpserver/listener/nio/AbstractListener.java
@@ -20,10 +20,12 @@
 package org.apache.ftpserver.listener.nio;
 
 import java.net.InetAddress;
-import java.util.Collections;
 import java.util.List;
 
 import org.apache.ftpserver.DataConnectionConfiguration;
+import org.apache.ftpserver.ipfilter.DefaultIpFilter;
+import org.apache.ftpserver.ipfilter.IpFilter;
+import org.apache.ftpserver.ipfilter.IpFilterType;
 import org.apache.ftpserver.listener.Listener;
 import org.apache.ftpserver.listener.ListenerFactory;
 import org.apache.ftpserver.ssl.SslConfiguration;
@@ -51,29 +53,62 @@
     private List<InetAddress> blockedAddresses;
 
     private List<Subnet> blockedSubnets;
+    
+    private IpFilter ipFilter = null;
 
     private DataConnectionConfiguration dataConnectionConfig;
 
     /**
+     * @deprecated Use the constructor with IpFilter instead. 
+     * Constructor for internal use, do not use directly. Instead use {@link ListenerFactory}
+     */
+    @Deprecated
+    public AbstractListener(String serverAddress, int port, boolean implicitSsl, 
+            SslConfiguration sslConfiguration, DataConnectionConfiguration dataConnectionConfig,
+            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> blockedSubnets) {
+    	this(serverAddress, port, implicitSsl, sslConfiguration, 
+    		dataConnectionConfig, idleTimeout, createBlackListFilter(blockedAddresses, blockedSubnets));
+    	this.blockedAddresses = blockedAddresses;
+    	this.blockedSubnets = blockedSubnets;
+    }
+    
+    /**
      * Constructor for internal use, do not use directly. Instead use {@link ListenerFactory}
      */
     public AbstractListener(String serverAddress, int port, boolean implicitSsl, 
             SslConfiguration sslConfiguration, DataConnectionConfiguration dataConnectionConfig,
-            int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> blockedSubnets) {
+            int idleTimeout, IpFilter ipFilter) {
         this.serverAddress = serverAddress;
         this.port = port;
         this.implicitSsl = implicitSsl;
         this.dataConnectionConfig = dataConnectionConfig;
         this.ssl = sslConfiguration;
         this.idleTimeout = idleTimeout;
-        
-        if(blockedAddresses != null) {
-            this.blockedAddresses = Collections.unmodifiableList(blockedAddresses);
-        }
-        if(blockedSubnets != null) {
-            this.blockedSubnets = Collections.unmodifiableList(blockedSubnets);
-        }
-        
+        this.ipFilter = ipFilter;
+    }
+    
+    /**
+     * Creates an IpFilter that blacklists the given IP addresses and/or Subnets. 
+     * @param blockedAddresses the addresses to block
+     * @param blockedSubnets the subnets to block
+     * @return an IpFilter that blacklists the given IP addresses and/or Subnets.
+     */
+    private static IpFilter createBlackListFilter(List<InetAddress> blockedAddresses, 
+    	List<Subnet> blockedSubnets) {
+    	if(blockedAddresses == null && blockedSubnets == null) {
+    		return null;
+    	}
+		//Initialize the IP filter with Deny type
+		DefaultIpFilter ipFilter = new DefaultIpFilter(IpFilterType.DENY);
+		if(blockedSubnets != null) {
+			ipFilter.addAll(blockedSubnets);
+		}
+		if(blockedAddresses != null) {
+			for(InetAddress address:blockedAddresses) {
+				ipFilter.add(new Subnet(address, 32));
+			}
+		}
+		return ipFilter;
     }
     
     /**
@@ -146,4 +181,8 @@
     public List<Subnet> getBlockedSubnets() {
         return blockedSubnets;
     }
+    
+    public IpFilter getIpFilter() {
+    	return ipFilter;
+    }
 }
diff --git a/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java b/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java
index 954a998..aec1077 100644
--- a/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java
+++ b/core/src/main/java/org/apache/ftpserver/listener/nio/NioListener.java
@@ -37,6 +37,8 @@
 import org.apache.ftpserver.impl.FtpHandler;
 import org.apache.ftpserver.impl.FtpIoSession;
 import org.apache.ftpserver.impl.FtpServerContext;
+import org.apache.ftpserver.ipfilter.IpFilter;
+import org.apache.ftpserver.ipfilter.MinaIpFilter;
 import org.apache.ftpserver.listener.Listener;
 import org.apache.ftpserver.listener.ListenerFactory;
 import org.apache.ftpserver.ssl.ClientAuth;
@@ -80,8 +82,10 @@
     private FtpServerContext context;
 
     /**
+     * @deprecated Use the constructor with IpFilter instead. 
      * Constructor for internal use, do not use directly. Instead use {@link ListenerFactory}
      */
+    @Deprecated
     public NioListener(String serverAddress, int port,
             boolean implicitSsl,
             SslConfiguration sslConfiguration,
@@ -89,27 +93,18 @@
             int idleTimeout, List<InetAddress> blockedAddresses, List<Subnet> blockedSubnets) {
         super(serverAddress, port, implicitSsl, sslConfiguration, dataConnectionConfig, 
                 idleTimeout, blockedAddresses, blockedSubnets);   
-        
-        updateBlacklistFilter();
     }
 
-    private void updateBlacklistFilter() {
-        if (acceptor != null) {
-            BlacklistFilter filter = (BlacklistFilter) acceptor
-                    .getFilterChain().get("ipFilter");
-
-            if (filter != null) {
-                if (getBlockedAddresses() != null) {
-                    filter.setBlacklist(getBlockedAddresses());
-                } else if (getBlockedSubnets() != null) {
-                    filter.setSubnetBlacklist(getBlockedSubnets());
-                } else {
-                    // an empty list clears the blocked addresses
-                    filter.setSubnetBlacklist(new ArrayList<Subnet>());
-                }
-
-            }
-        }
+    /**
+     * Constructor for internal use, do not use directly. Instead use {@link ListenerFactory}
+     */
+    public NioListener(String serverAddress, int port,
+            boolean implicitSsl,
+            SslConfiguration sslConfiguration,
+            DataConnectionConfiguration dataConnectionConfig, 
+            int idleTimeout, IpFilter ipFilter) {
+        super(serverAddress, port, implicitSsl, sslConfiguration, dataConnectionConfig, 
+                idleTimeout, ipFilter);   
     }
 
     /**
@@ -141,9 +136,11 @@
     
             acceptor.getFilterChain().addLast("mdcFilter", mdcFilter);
     
-            // add and update the blacklist filter
-            acceptor.getFilterChain().addLast("ipFilter", new BlacklistFilter());
-            updateBlacklistFilter();
+            IpFilter ipFilter = getIpFilter();
+            if(ipFilter != null) {
+            // 	add and IP filter to the filter chain. 
+            	acceptor.getFilterChain().addLast("ipFilter", new MinaIpFilter(ipFilter));
+            }
     
             acceptor.getFilterChain().addLast("threadPool",
                     new ExecutorFilter(filterExecutor));
diff --git a/core/src/main/resources/org/apache/ftpserver/config/spring/ftpserver-1.0.xsd b/core/src/main/resources/org/apache/ftpserver/config/spring/ftpserver-1.0.xsd
index 74fd74d..0e99d26 100644
--- a/core/src/main/resources/org/apache/ftpserver/config/spring/ftpserver-1.0.xsd
+++ b/core/src/main/resources/org/apache/ftpserver/config/spring/ftpserver-1.0.xsd
@@ -99,6 +99,24 @@
 		</xs:complexType>

 	</xs:element>

 

+	<!-- Element used to configure the IP Filtering -->

+	<xs:element name="ip-filter">

+		<xs:complexType>

+			<xs:simpleContent>

+				<xs:extension base="xs:string">

+					<xs:attribute name="type">

+						<xs:simpleType>

+							<xs:restriction base="xs:string">

+								<xs:enumeration value="allow" />

+								<xs:enumeration value="deny" />

+							</xs:restriction>

+						</xs:simpleType>

+					</xs:attribute>

+				</xs:extension>

+			</xs:simpleContent>

+		</xs:complexType>

+	</xs:element>

+

 	<!-- Element used to define the default, NIO based listener -->

 	<xs:element name="nio-listener">

 		<xs:complexType>

@@ -130,6 +148,7 @@
 					</xs:complexType>

 				</xs:element>

 				<xs:element minOccurs="0" name="blacklist" type="xs:string" />

+				<xs:element ref="ip-filter" minOccurs="0" maxOccurs="1" />

 			</xs:sequence>

 			<xs:attribute name="name" use="required" type="xs:string" />

 			<xs:attribute name="local-address" />

diff --git a/core/src/test/java/org/apache/ftpserver/clienttests/IpFilterTest.java b/core/src/test/java/org/apache/ftpserver/clienttests/IpFilterTest.java
new file mode 100644
index 0000000..3f3c02c
--- /dev/null
+++ b/core/src/test/java/org/apache/ftpserver/clienttests/IpFilterTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.clienttests;
+
+import java.net.InetAddress;
+
+import org.apache.commons.net.ftp.FTPConnectionClosedException;
+import org.apache.ftpserver.FtpServerFactory;
+import org.apache.ftpserver.ipfilter.DefaultIpFilter;
+import org.apache.ftpserver.ipfilter.IpFilterType;
+import org.apache.ftpserver.listener.ListenerFactory;
+import org.apache.mina.filter.firewall.Subnet;
+import org.springframework.context.annotation.FilterType;
+
+/**
+*
+* @author <a href="http://mina.apache.org">Apache MINA Project</a>
+*
+*/
+public class IpFilterTest extends ClientTestTemplate {
+	
+	private DefaultIpFilter filter = new DefaultIpFilter(IpFilterType.DENY);
+	
+    protected FtpServerFactory createServer() throws Exception {
+        FtpServerFactory server = super.createServer();
+
+        ListenerFactory factory = new ListenerFactory(server.getListener("default"));
+
+        factory.setIpFilter(filter);
+        server.addListener("default", factory.createListener());
+        
+        return server;
+    }
+
+    protected boolean isConnectClient() {
+        return false;
+    }
+
+    public void testDenyBlackList() throws Exception {
+    	filter.clear();
+    	filter.setType(IpFilterType.DENY);
+        filter.add(new Subnet(InetAddress.getByName("localhost"), 32));
+        try {
+            client.connect("localhost", getListenerPort());
+            fail("Must throw");
+        } catch (FTPConnectionClosedException e) {
+            // OK
+        }
+    }
+
+    public void testDenyEmptyWhiteList() throws Exception {
+    	filter.clear();
+    	filter.setType(IpFilterType.ALLOW);
+        try {
+            client.connect("localhost", getListenerPort());
+            fail("Must throw");
+        } catch (FTPConnectionClosedException e) {
+            // OK
+        }
+    }
+
+    public void testWhiteList() throws Exception {
+    	filter.clear();
+    	filter.setType(IpFilterType.ALLOW);
+        filter.add(new Subnet(InetAddress.getByName("localhost"), 32));
+        client.connect("localhost", getListenerPort());
+    }
+}
diff --git a/core/src/test/java/org/apache/ftpserver/config/spring/MyCustomListener.java b/core/src/test/java/org/apache/ftpserver/config/spring/MyCustomListener.java
index a9b9301..20b7d21 100644
--- a/core/src/test/java/org/apache/ftpserver/config/spring/MyCustomListener.java
+++ b/core/src/test/java/org/apache/ftpserver/config/spring/MyCustomListener.java
@@ -26,6 +26,7 @@
 import org.apache.ftpserver.DataConnectionConfiguration;
 import org.apache.ftpserver.impl.FtpIoSession;
 import org.apache.ftpserver.impl.FtpServerContext;
+import org.apache.ftpserver.ipfilter.IpFilter;
 import org.apache.ftpserver.listener.Listener;
 import org.apache.ftpserver.ssl.SslConfiguration;
 import org.apache.mina.filter.firewall.Subnet;
@@ -104,4 +105,8 @@
         return null;
     }
 
+	public IpFilter getIpFilter() {
+		return null;
+	}
+
 }
diff --git a/core/src/test/java/org/apache/ftpserver/config/spring/SpringConfigTest.java b/core/src/test/java/org/apache/ftpserver/config/spring/SpringConfigTest.java
index cd02c1b..cda1597 100644
--- a/core/src/test/java/org/apache/ftpserver/config/spring/SpringConfigTest.java
+++ b/core/src/test/java/org/apache/ftpserver/config/spring/SpringConfigTest.java
@@ -30,6 +30,7 @@
 import org.apache.ftpserver.command.impl.STAT;
 import org.apache.ftpserver.filesystem.nativefs.NativeFileSystemFactory;
 import org.apache.ftpserver.impl.DefaultFtpServer;
+import org.apache.ftpserver.ipfilter.DefaultIpFilter;
 import org.apache.ftpserver.listener.Listener;
 import org.apache.ftpserver.listener.nio.NioListener;
 import org.apache.mina.filter.firewall.Subnet;
@@ -79,16 +80,12 @@
                 .getDataConnectionConfiguration().getPassivePorts());
         assertEquals(false, ((NioListener) listener)
                 .getDataConnectionConfiguration().isPassiveIpCheck());
-
-        List<Subnet> subnets = ((NioListener) listener).getBlockedSubnets();
-        assertEquals(3, subnets.size());
-        assertEquals(new Subnet(InetAddress.getByName("1.2.3.0"), 16), subnets
-                .get(0));
-        assertEquals(new Subnet(InetAddress.getByName("1.2.4.0"), 16), subnets
-                .get(1));
-        assertEquals(new Subnet(InetAddress.getByName("1.2.3.4"), 32), subnets
-                .get(2));
-
+        
+        DefaultIpFilter filter = (DefaultIpFilter) listener.getIpFilter();
+        assertEquals(3, filter.size());
+        assertTrue(filter.contains(new Subnet(InetAddress.getByName("1.2.3.0"), 16)));
+        assertTrue(filter.contains(new Subnet(InetAddress.getByName("1.2.4.0"), 16)));
+        assertTrue(filter.contains(new Subnet(InetAddress.getByName("1.2.3.4"), 32)));
         listener = listeners.get("listener1");
         assertNotNull(listener);
         assertTrue(listener instanceof MyCustomListener);