View Javadoc

1   /*
2    * ------------------------------------------------------------------------------
3    * Hermes FTP Server
4    * Copyright (c) 2005-2007 Lars Behnke
5    * ------------------------------------------------------------------------------
6    * 
7    * This file is part of Hermes FTP Server.
8    * 
9    * Hermes FTP Server is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   * 
14   * Hermes FTP Server is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public License
20   * along with Hermes FTP Server; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22   * ------------------------------------------------------------------------------
23   */
24  
25  
26  package net.sf.hermesftp.cmd;
27  
28  import java.io.IOException;
29  import java.net.Inet4Address;
30  import java.net.Inet6Address;
31  import java.net.InetAddress;
32  import java.net.ServerSocket;
33  import java.net.Socket;
34  
35  import javax.net.ServerSocketFactory;
36  import javax.net.ssl.SSLServerSocket;
37  import javax.net.ssl.SSLServerSocketFactory;
38  
39  import net.sf.hermesftp.common.FtpConstants;
40  import net.sf.hermesftp.common.FtpSessionContext;
41  import net.sf.hermesftp.utils.IOUtils;
42  
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  /***
47   * Provides the data transfer socket for transfer passive mode.
48   * 
49   * @author Behnke
50   */
51  public class PassiveModeSocketProvider implements SocketProvider {
52  
53      private static final int  MAX_BIND_RETRIES     = 3;
54  
55      private static final int  DATA_CHANNEL_TIMEOUT = 10000;
56  
57      private static Log        log                  = LogFactory.getLog(PassiveModeSocketProvider.class);
58  
59      private FtpSessionContext ctx;
60  
61      private ServerSocket      serverSocket;
62  
63      private Socket            dataSocket;
64  
65      private int               preferredProtocol;
66  
67      /***
68       * Constructor.
69       * 
70       * @param ctx Session context.
71       * @param preferredProtocol Preferred protocol (IPv4 or IPv6)
72       */
73      public PassiveModeSocketProvider(FtpSessionContext ctx, int preferredProtocol) {
74          this.ctx = ctx;
75          this.preferredProtocol = preferredProtocol;
76      }
77  
78      /***
79       * {@inheritDoc}
80       */
81      public DataChannelInfo init() throws IOException {
82  
83          /* Get local machine address and check protocol version. */
84          InetAddress localIp = ctx.getClientSocket().getLocalAddress();
85          int currentProtocol = getProtocolIdxByAddr(localIp);
86          boolean ok = (preferredProtocol == currentProtocol) || (preferredProtocol == 0);
87          if (!ok) {
88              throw new IOException("Invalid IP version");
89          }
90  
91          /* Get the next available port */
92          int retries = MAX_BIND_RETRIES;
93          while (retries > 0) {
94              Integer port = ctx.getNextPassivePort();
95              port = port == null ? new Integer(0) : port;
96              try {
97                  log.debug("Trying to bind server socket to port " + port);
98                  serverSocket = createServerSocket(localIp, port.intValue());
99                  break;
100             } catch (Exception e) {
101                 retries--;
102                 log.debug("Binding server socket to port " + port + " failed.");
103             }
104         }
105         if (serverSocket == null) {
106             throw new IOException("Initializing server socket failed.");
107         }
108 
109         /* Wrap up connection parameter */
110         log.debug("Server socket successfully bound to port " + serverSocket.getLocalPort() + ".");
111         return new DataChannelInfo(localIp.getHostAddress(), serverSocket.getLocalPort());
112 
113     }
114 
115     /***
116      * {@inheritDoc}
117      */
118     public void closeSocket() {
119         IOUtils.closeGracefully(serverSocket);
120         IOUtils.closeGracefully(dataSocket);
121         serverSocket = null;
122         dataSocket = null;
123 
124     }
125 
126     /***
127      * {@inheritDoc}
128      */
129     public Socket provideSocket() throws IOException {
130         if (dataSocket == null) {
131             if (serverSocket == null) {
132                 throw new IOException("Server socket not initialized.");
133             }
134             dataSocket = serverSocket.accept();
135         }
136         return dataSocket;
137     }
138 
139     private int getProtocolIdxByAddr(InetAddress addr) {
140         if (addr instanceof Inet4Address) {
141             return 1;
142         } else if (addr instanceof Inet6Address) {
143             return 2;
144         } else {
145             return 0;
146         }
147     }
148 
149     /***
150      * Creates the server socket that accepts the data connection.
151      * 
152      * @param localIp The local IP address.
153      * @param port The port.
154      * @return The server socket.
155      * @throws IOException Error on creating server socket.
156      */
157     private ServerSocket createServerSocket(InetAddress localIp, int port) throws IOException {
158         ServerSocket sock;
159         Boolean dataProtection = (Boolean) ctx.getAttribute(FtpConstants.ATTR_DATA_PROT);
160         boolean ssl = dataProtection != null && dataProtection.booleanValue();
161         if (ssl) {
162             SSLServerSocketFactory factory = ctx.getOptions().getSslContext().getServerSocketFactory();
163             SSLServerSocket sslServerSocket = (SSLServerSocket) factory.createServerSocket(port, 1, localIp);
164             sslServerSocket.setUseClientMode(false);
165             enableCipherSuites(sslServerSocket);
166             sock = sslServerSocket;
167         } else {
168             sock = ServerSocketFactory.getDefault().createServerSocket(port, 1, localIp);
169         }
170         sock.setSoTimeout(DATA_CHANNEL_TIMEOUT);
171         return sock;
172     }
173 
174     /***
175      * Enables the configured cipher suites in the passed server socket.
176      * 
177      * @param sslServerSocket The server socket.
178      */
179     private void enableCipherSuites(SSLServerSocket sslServerSocket) {
180         String[] cipherSuites = ctx.getOptions().getStringArray(FtpConstants.OPT_SSL_CIPHER_SUITES, null);
181         if (cipherSuites != null) {
182             if (cipherSuites.length == 1 && FtpConstants.WILDCARD.equals(cipherSuites[0])) {
183                 sslServerSocket.setEnabledCipherSuites(sslServerSocket.getSupportedCipherSuites());
184             } else {
185                 sslServerSocket.setEnabledCipherSuites(cipherSuites);
186             }
187         }
188     }
189 }