com.apporiented.hermesftp.cmd.PassiveModeSocketProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.apporiented.hermesftp.cmd.PassiveModeSocketProvider.java

Source

/*
 * ------------------------------------------------------------------------------
 * Hermes FTP Server
 * Copyright (c) 2005-2014 Lars Behnke
 * ------------------------------------------------------------------------------
 * 
 * This file is part of Hermes FTP Server.
 * 
 * Hermes FTP Server is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Hermes FTP Server is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Hermes FTP Server; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 */

package com.apporiented.hermesftp.cmd;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;

import com.apporiented.hermesftp.common.FtpConstants;
import com.apporiented.hermesftp.common.FtpSessionContext;
import com.apporiented.hermesftp.utils.IOUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides the data transfer socket for transfer passive mode.
 * 
 * @author Behnke
 */
public class PassiveModeSocketProvider implements SocketProvider {

    private static final int MAX_BIND_RETRIES = 3;

    private static final int DATA_CHANNEL_TIMEOUT = 10000;

    private static Log log = LogFactory.getLog(PassiveModeSocketProvider.class);

    private FtpSessionContext ctx;

    private ServerSocket serverSocket;

    private Socket dataSocket;

    private int preferredProtocol;

    /**
     * Constructor.
     * 
     * @param ctx Session context.
     * @param preferredProtocol Preferred protocol (IPv4 or IPv6)
     */
    public PassiveModeSocketProvider(FtpSessionContext ctx, int preferredProtocol) {
        this.ctx = ctx;
        this.preferredProtocol = preferredProtocol;
    }

    /**
     * {@inheritDoc}
     */
    public DataChannelInfo init() throws IOException {

        /* Get local machine address and check protocol version. */
        InetAddress localIp = ctx.getClientSocket().getLocalAddress();
        int currentProtocol = getProtocolIdxByAddr(localIp);
        boolean ok = (preferredProtocol == currentProtocol) || (preferredProtocol == 0);
        if (!ok) {
            throw new IOException("Invalid IP version");
        }

        /* Get the next available port */
        int retries = MAX_BIND_RETRIES;
        while (retries > 0) {
            Integer port = ctx.getNextPassivePort();
            port = port == null ? new Integer(0) : port;
            try {
                log.debug("Trying to bind server socket to port " + port);
                serverSocket = createServerSocket(localIp, port);
                break;
            } catch (Exception e) {
                retries--;
                log.debug("Binding server socket to port " + port + " failed.");
            }
        }
        if (serverSocket == null) {
            throw new IOException("Initializing server socket failed.");
        }

        /* Wrap up connection parameter */
        log.debug("Server socket successfully bound to port " + serverSocket.getLocalPort() + ".");
        return new DataChannelInfo(localIp.getHostAddress(), serverSocket.getLocalPort());

    }

    /**
     * {@inheritDoc}
     */
    public void closeSocket() {
        IOUtils.closeGracefully(serverSocket);
        IOUtils.closeGracefully(dataSocket);
        serverSocket = null;
        dataSocket = null;

    }

    /**
     * {@inheritDoc}
     */
    public Socket provideSocket() throws IOException {
        if (dataSocket == null) {
            if (serverSocket == null) {
                throw new IOException("Server socket not initialized.");
            }
            dataSocket = serverSocket.accept();
        }
        return dataSocket;
    }

    private int getProtocolIdxByAddr(InetAddress addr) {
        if (addr instanceof Inet4Address) {
            return 1;
        } else if (addr instanceof Inet6Address) {
            return 2;
        } else {
            return 0;
        }
    }

    /**
     * Creates the server socket that accepts the data connection.
     * 
     * @param localIp The local IP address.
     * @param port The port.
     * @return The server socket.
     * @throws IOException Error on creating server socket.
     */
    private ServerSocket createServerSocket(InetAddress localIp, int port) throws IOException {
        ServerSocket sock;
        Boolean dataProtection = (Boolean) ctx.getAttribute(FtpConstants.ATTR_DATA_PROT);
        boolean ssl = dataProtection != null && dataProtection;
        if (ssl) {
            SSLServerSocketFactory factory = ctx.getOptions().getSslContext().getServerSocketFactory();
            SSLServerSocket sslServerSocket = (SSLServerSocket) factory.createServerSocket(port, 1, localIp);
            sslServerSocket.setUseClientMode(false);
            enableCipherSuites(sslServerSocket);
            sock = sslServerSocket;
        } else {
            sock = ServerSocketFactory.getDefault().createServerSocket(port, 1, localIp);
        }
        sock.setSoTimeout(DATA_CHANNEL_TIMEOUT);
        return sock;
    }

    /**
     * Enables the configured cipher suites in the passed server socket.
     * 
     * @param sslServerSocket The server socket.
     */
    private void enableCipherSuites(SSLServerSocket sslServerSocket) {
        String[] cipherSuites = ctx.getOptions().getStringArray(FtpConstants.OPT_SSL_CIPHER_SUITES, null);
        if (cipherSuites != null) {
            if (cipherSuites.length == 1 && FtpConstants.WILDCARD.equals(cipherSuites[0])) {
                sslServerSocket.setEnabledCipherSuites(sslServerSocket.getSupportedCipherSuites());
            } else {
                sslServerSocket.setEnabledCipherSuites(cipherSuites);
            }
        }
    }
}