Added check for passive ports allocation to check if the port is not bound by another application (FTPSERVER-302)

git-svn-id: https://svn.apache.org/repos/asf/mina/ftpserver/trunk@782448 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/ftpserver/DataConnectionConfigurationFactory.java b/core/src/main/java/org/apache/ftpserver/DataConnectionConfigurationFactory.java
index 8c7e877..5a0aa2c 100644
--- a/core/src/main/java/org/apache/ftpserver/DataConnectionConfigurationFactory.java
+++ b/core/src/main/java/org/apache/ftpserver/DataConnectionConfigurationFactory.java
@@ -48,7 +48,7 @@
     
     private String passiveAddress;
     private String passiveExternalAddress;
-    private PassivePorts passivePorts = new PassivePorts(new int[] { 0 });
+    private PassivePorts passivePorts = new PassivePorts(new int[] { 0 }, true);
     private boolean implicitSsl;
 
     /**
@@ -249,7 +249,7 @@
      * @param passivePorts The passive ports string
      */
     public void setPassivePorts(String passivePorts) {
-        this.passivePorts = new PassivePorts(passivePorts);
+        this.passivePorts = new PassivePorts(passivePorts, true);
     }
 
     
diff --git a/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java b/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java
index 66224ac..19e5e96 100644
--- a/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java
+++ b/core/src/main/java/org/apache/ftpserver/impl/IODataConnectionFactory.java
@@ -154,8 +154,7 @@
     }
 
     /**
-     * Initiate a data connection in passive mode (server listening). It returns
-     * the success flag.
+     * Initiate a data connection in passive mode (server listening). 
      */
     public synchronized InetSocketAddress initPassiveDataConnection()
             throws DataConnectionException {
diff --git a/core/src/main/java/org/apache/ftpserver/impl/PassivePorts.java b/core/src/main/java/org/apache/ftpserver/impl/PassivePorts.java
index 7bed30e..2750c0e 100644
--- a/core/src/main/java/org/apache/ftpserver/impl/PassivePorts.java
+++ b/core/src/main/java/org/apache/ftpserver/impl/PassivePorts.java
@@ -19,6 +19,8 @@
 
 package org.apache.ftpserver.impl;
 
+import java.io.IOException;
+import java.net.ServerSocket;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -42,6 +44,8 @@
 
     private String passivePortsString;
 
+    private boolean checkIfBound;
+    
     /**
      * Parse a string containing passive ports
      * 
@@ -145,13 +149,13 @@
         }
     }
 
-    public PassivePorts(final String passivePorts) {
-        this(parse(passivePorts));
+    public PassivePorts(final String passivePorts, boolean checkIfBound) {
+        this(parse(passivePorts), checkIfBound);
 
         this.passivePortsString = passivePorts;
     }
 
-    public PassivePorts(final int[] passivePorts) {
+    public PassivePorts(final int[] passivePorts, boolean checkIfBound) {
         if (passivePorts != null) {
             this.passivePorts = passivePorts.clone();
         } else {
@@ -159,12 +163,44 @@
         }
 
         reservedPorts = new boolean[passivePorts.length];
+        this.checkIfBound = checkIfBound;
     }
 
+    private boolean checkPortUnbound(int port) {
+        // is this check disabled?
+        if(!checkIfBound) {
+            return true;
+        }
+        
+        // if using 0 port, it will always be available
+        if(port == 0) {
+            return true;
+        }
+        
+        ServerSocket ss = null;
+        try {
+            ss = new ServerSocket(port);
+            ss.setReuseAddress(true);
+            return true;
+        } catch (IOException e) {
+            // port probably in used, check next
+            return false;
+        } finally {
+            if(ss != null) {
+                try {
+                    ss.close();
+                } catch (IOException e) {
+                    // could not close, check next
+                    return false;
+                }
+            }
+        }
+    }
+    
     public int reserveNextPort() {
         // search for a free port
         for (int i = 0; i < passivePorts.length; i++) {
-            if (!reservedPorts[i]) {
+            if (!reservedPorts[i] && checkPortUnbound(passivePorts[i])) {
                 if (passivePorts[i] != 0) {
                     reservedPorts[i] = true;
                 }
diff --git a/core/src/main/java/org/apache/ftpserver/impl/ServerDataConnectionFactory.java b/core/src/main/java/org/apache/ftpserver/impl/ServerDataConnectionFactory.java
index d07344c..6e06e70 100644
--- a/core/src/main/java/org/apache/ftpserver/impl/ServerDataConnectionFactory.java
+++ b/core/src/main/java/org/apache/ftpserver/impl/ServerDataConnectionFactory.java
@@ -39,7 +39,7 @@
     void initActiveDataConnection(InetSocketAddress address);
 
     /**
-     * Initate the passive data connection.
+     * Initiate the passive data connection.
      * 
      * @return The {@link InetSocketAddress} on which the data connection if
      *         bound.
diff --git a/core/src/test/java/org/apache/ftpserver/clienttests/PasvUsedPortTest.java b/core/src/test/java/org/apache/ftpserver/clienttests/PasvUsedPortTest.java
new file mode 100644
index 0000000..b838ab7
--- /dev/null
+++ b/core/src/test/java/org/apache/ftpserver/clienttests/PasvUsedPortTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.ServerSocket;
+
+import org.apache.ftpserver.DataConnectionConfigurationFactory;
+import org.apache.ftpserver.FtpServerFactory;
+import org.apache.ftpserver.listener.ListenerFactory;
+import org.apache.ftpserver.test.TestUtil;
+
+/**
+*
+* @author The Apache MINA Project (dev@mina.apache.org)
+*
+*/
+public class PasvUsedPortTest extends ClientTestTemplate {
+
+    private int passivePort;
+
+    @Override
+    protected FtpServerFactory createServer() throws Exception {
+        FtpServerFactory server = super.createServer();
+
+        ListenerFactory listenerFactory = new ListenerFactory(server
+                .getListener("default"));
+
+        DataConnectionConfigurationFactory dccFactory = new DataConnectionConfigurationFactory();
+
+        passivePort = TestUtil.findFreePort(12444);
+
+        dccFactory.setPassivePorts(passivePort + "-" + (passivePort + 1));
+
+        listenerFactory.setDataConnectionConfiguration(dccFactory
+                .createDataConnectionConfiguration());
+
+        server.addListener("default", listenerFactory.createListener());
+
+        return server;
+    }
+
+    public void testPasvWithUsedPort() throws Exception {
+        // bind to the first available passive port
+        ServerSocket ss = new ServerSocket(passivePort);
+        
+        client.login(ADMIN_USERNAME, ADMIN_PASSWORD);
+        client.pasv();
+        assertEquals("227 Entering Passive Mode (127,0,0,1,48,157)", client.getReplyString().trim());
+        client.quit();
+        client.disconnect();
+
+        // close blocking socket
+        ss.close();
+        
+        client.connect("localhost", getListenerPort());
+        client.login(ADMIN_USERNAME, ADMIN_PASSWORD);
+        client.pasv();
+        assertEquals("227 Entering Passive Mode (127,0,0,1,48,156)", client.getReplyString().trim());
+    }
+}
diff --git a/core/src/test/java/org/apache/ftpserver/impl/PassivePortsTest.java b/core/src/test/java/org/apache/ftpserver/impl/PassivePortsTest.java
index 1d3218b..2c3ffc0 100644
--- a/core/src/test/java/org/apache/ftpserver/impl/PassivePortsTest.java
+++ b/core/src/test/java/org/apache/ftpserver/impl/PassivePortsTest.java
@@ -29,21 +29,21 @@
 public class PassivePortsTest extends TestCase {
 
     public void testParseSingleValue() {
-        PassivePorts ports = new PassivePorts("123");
+        PassivePorts ports = new PassivePorts("123", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(-1, ports.reserveNextPort());
     }
 
     public void testParseMaxValue() {
-        PassivePorts ports = new PassivePorts("65535");
+        PassivePorts ports = new PassivePorts("65535", false);
 
         assertEquals(65535, ports.reserveNextPort());
         assertEquals(-1, ports.reserveNextPort());
     }
 
     public void testParseMinValue() {
-        PassivePorts ports = new PassivePorts("0");
+        PassivePorts ports = new PassivePorts("0", false);
 
         assertEquals(0, ports.reserveNextPort());
         assertEquals(0, ports.reserveNextPort());
@@ -54,7 +54,7 @@
 
     public void testParseTooLargeValue() {
         try {
-            new PassivePorts("65536");
+            new PassivePorts("65536", false);
             fail("Must fail due to too high port number");
         } catch (IllegalArgumentException e) {
             // ok
@@ -63,7 +63,7 @@
 
     public void testParseNonNumericValue() {
         try {
-            new PassivePorts("foo");
+            new PassivePorts("foo", false);
             fail("Must fail due to non numerical port number");
         } catch (IllegalArgumentException e) {
             // ok
@@ -71,7 +71,7 @@
     }
 
     public void testParseListOfValues() {
-        PassivePorts ports = new PassivePorts("123, 456,\t\n789");
+        PassivePorts ports = new PassivePorts("123, 456,\t\n789", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(456, ports.reserveNextPort());
@@ -80,7 +80,7 @@
     }
 
     public void testParseListOfValuesOrder() {
-        PassivePorts ports = new PassivePorts("123, 789, 456");
+        PassivePorts ports = new PassivePorts("123, 789, 456", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(789, ports.reserveNextPort());
@@ -89,7 +89,7 @@
     }
 
     public void testParseListOfValuesDuplicate() {
-        PassivePorts ports = new PassivePorts("123, 789, 456, 789");
+        PassivePorts ports = new PassivePorts("123, 789, 456, 789", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(789, ports.reserveNextPort());
@@ -98,7 +98,7 @@
     }
 
     public void testParseSimpleRange() {
-        PassivePorts ports = new PassivePorts("123-125");
+        PassivePorts ports = new PassivePorts("123-125", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(124, ports.reserveNextPort());
@@ -107,7 +107,7 @@
     }
 
     public void testParseMultipleRanges() {
-        PassivePorts ports = new PassivePorts("123-125, 127-128, 130-132");
+        PassivePorts ports = new PassivePorts("123-125, 127-128, 130-132", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(124, ports.reserveNextPort());
@@ -121,7 +121,7 @@
     }
 
     public void testParseMixedRangeAndSingle() {
-        PassivePorts ports = new PassivePorts("123-125, 126, 128-129");
+        PassivePorts ports = new PassivePorts("123-125, 126, 128-129", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(124, ports.reserveNextPort());
@@ -133,7 +133,7 @@
     }
 
     public void testParseOverlapingRanges() {
-        PassivePorts ports = new PassivePorts("123-125, 124-126");
+        PassivePorts ports = new PassivePorts("123-125, 124-126", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(124, ports.reserveNextPort());
@@ -143,7 +143,7 @@
     }
 
     public void testParseOverlapingRangesorder() {
-        PassivePorts ports = new PassivePorts("124-126, 123-125");
+        PassivePorts ports = new PassivePorts("124-126, 123-125", false);
 
         assertEquals(124, ports.reserveNextPort());
         assertEquals(125, ports.reserveNextPort());
@@ -153,7 +153,7 @@
     }
 
     public void testParseOpenLowerRange() {
-        PassivePorts ports = new PassivePorts("9, -3");
+        PassivePorts ports = new PassivePorts("9, -3", false);
 
         assertEquals(9, ports.reserveNextPort());
         assertEquals(1, ports.reserveNextPort());
@@ -163,7 +163,7 @@
     }
 
     public void testParseOpenUpperRange() {
-        PassivePorts ports = new PassivePorts("65533-");
+        PassivePorts ports = new PassivePorts("65533-", false);
 
         assertEquals(65533, ports.reserveNextPort());
         assertEquals(65534, ports.reserveNextPort());
@@ -172,7 +172,7 @@
     }
 
     public void testParseOpenUpperRange3() {
-        PassivePorts ports = new PassivePorts("65533-, 65532-");
+        PassivePorts ports = new PassivePorts("65533-, 65532-", false);
 
         assertEquals(65533, ports.reserveNextPort());
         assertEquals(65534, ports.reserveNextPort());
@@ -182,7 +182,7 @@
     }
 
     public void testParseOpenUpperRange2() {
-        PassivePorts ports = new PassivePorts("65533-, 1");
+        PassivePorts ports = new PassivePorts("65533-, 1", false);
 
         assertEquals(65533, ports.reserveNextPort());
         assertEquals(65534, ports.reserveNextPort());
@@ -192,7 +192,7 @@
     }
 
     public void testParseRelease() {
-        PassivePorts ports = new PassivePorts("123, 456,789");
+        PassivePorts ports = new PassivePorts("123, 456,789", false);
 
         assertEquals(123, ports.reserveNextPort());
         assertEquals(456, ports.reserveNextPort());