/*
* ------------------------------------------------------------------------------
* 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.server;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.apporiented.hermesftp.common.FtpConstants;
import com.apporiented.hermesftp.common.FtpEventListener;
import com.apporiented.hermesftp.common.FtpServerOptions;
import com.apporiented.hermesftp.common.FtpSessionContext;
import com.apporiented.hermesftp.session.FtpSession;
import com.apporiented.hermesftp.usermanager.UserManager;
import com.apporiented.hermesftp.utils.AbstractAppAwareBean;
import com.apporiented.hermesftp.utils.IOUtils;
import com.apporiented.hermesftp.utils.NetUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Ancestor class for FTP server implementations.
*
* @author Lars Behnke
*/
public abstract class AbstractFtpServer extends AbstractAppAwareBean implements FtpServer, FtpConstants,
FtpEventListener {
private static final int DEFAULT_TIMEOUT = 3000;
private static Log log = LogFactory.getLog(FtpServer.class);
private List<FtpSession> sessions = new ArrayList<FtpSession>();
private boolean terminated;
private FtpServerOptions options;
private String resources;
private String name;
private int status = SERVER_STATUS_UNDEF;
private UserManager userManager;
private List<FtpEventListener> ftpEventListeners = new ArrayList<FtpEventListener>();
private int connectionCountHWMark;
private Date connectionCountHWMarkDate = new Date();
/**
* Creates a server socket. Depending on the server implementation this can be a SSL or a
* regular server socket.
*
* @return The server socket.
* @throws IOException Error on creating server socket.
*/
protected abstract ServerSocket createServerSocket() throws IOException;
/**
* Creates the context object passed to the user session.
*
* @return The session context.
*/
protected abstract FtpSessionContext createFtpContext();
/**
* Halts the server.
*/
public void abort() {
this.terminated = true;
}
/**
* Getter method for the java bean <code>options</code>.
*
* @return Returns the value of the java bean <code>options</code>.
*/
public FtpServerOptions getOptions() {
return options;
}
/**
* Setter method for the java bean <code>options</code>.
*
* @param options The value of options to set.
*/
public void setOptions(FtpServerOptions options) {
this.options = options;
}
/**
* {@inheritDoc}
*/
public void run() {
setStatus(SERVER_STATUS_INIT);
ServerSocket serverSocket = null;
try {
getUserManager().load();
serverSocket = createServerSocket();
serverSocket.setSoTimeout(DEFAULT_TIMEOUT);
setStatus(SERVER_STATUS_READY);
while (!isTerminated()) {
Socket clientSocket;
try {
clientSocket = serverSocket.accept();
} catch (SocketTimeoutException e) {
continue;
}
/* Check blacklisted IP v4 addresses */
InetAddress clientAddr = clientSocket.getInetAddress();
InetAddress localAddr = clientSocket.getLocalAddress();
log.info("Client requests connection. ClientAddr.: " + clientAddr + ", LocalAddr.: " + localAddr);
String listKey = NetUtils.isIPv6(clientAddr) ? FtpConstants.OPT_IPV6_BLACK_LIST : FtpConstants.OPT_IPV4_BLACK_LIST;
String ipBlackList = getOptions().getString(listKey, "");
if (NetUtils.checkIPMatch(ipBlackList, clientAddr)) {
log.info("Client with IP address " + clientAddr.getHostAddress() + " rejected (blacklisted).");
IOUtils.closeGracefully(clientSocket);
continue;
}
/* Initialize session context */
FtpSessionContext ctx = createFtpContext();
ctx.check();
ctx.setCreationTime(new Date());
ctx.setClientSocket(clientSocket);
FtpSession session = (FtpSession) getApplicationContext().getBean(BEAN_SESSION);
session.setFtpContext(ctx);
/* Start session */
log.debug("Accepting connection to " + clientAddr.getHostAddress());
session.start();
registerSession(session);
}
setStatus(SERVER_STATUS_HALTED);
} catch (IOException e) {
setStatus(SERVER_STATUS_UNDEF);
log.error(e, e);
} finally {
terminateAllClientSessions();
IOUtils.closeGracefully(serverSocket);
}
}
private void registerSession(FtpSession session) {
synchronized (this) {
sessions.add(session);
int sessionCount = getConnectionCount();
if (sessionCount >= connectionCountHWMark) {
connectionCountHWMark = sessionCount;
connectionCountHWMarkDate = new Date();
}
}
}
/**
* Getter method for the java bean <code>connectionCount</code>.
*
* @return Returns the value of the java bean <code>connectionCount</code>.
*/
public int getConnectionCount() {
synchronized (this) {
cleanUpSessions();
return sessions.size();
}
}
/**
* {@inheritDoc}
*/
public void cleanUpSessions() {
List<FtpSession> newList = new ArrayList<FtpSession>();
for (FtpSession session : sessions) {
if (!session.isTerminated()) {
newList.add(session);
}
}
sessions = newList;
}
private void terminateAllClientSessions() {
for (FtpSession session : sessions) {
session.abort();
}
}
/**
* Convenience method for accessing the application properties.
*
* @param name The name of the requested property.
* @return The property.
*/
public String getOption(String name) {
return getOptions().getProperty(name);
}
/**
* Getter method for the java bean <code>resources</code>.
*
* @return Returns the value of the java bean <code>resources</code>.
*/
public String getResources() {
return resources;
}
/**
* Setter method for the java bean <code>resources</code>.
*
* @param resources The value of resources to set.
*/
public void setResources(String resources) {
this.resources = resources;
}
/**
* Getter method for the java bean <code>terminated</code>.
*
* @return Returns the value of the java bean <code>terminated</code>.
*/
public boolean isTerminated() {
return terminated;
}
/**
* Getter method for the java bean <code>status</code>.
*
* @return Returns the value of the java bean <code>status</code>.
*/
public int getStatus() {
return status;
}
/**
* Setter method for the java bean <code>status</code>.
*
* @param status The value of status to set.
*/
public void setStatus(int status) {
this.status = status;
}
/**
* Getter method for the java bean <code>userManager</code>.
*
* @return Returns the value of the java bean <code>userManager</code>.
*/
public UserManager getUserManager() {
return userManager;
}
/**
* Setter method for the java bean <code>userManager</code>.
*
* @param userManager The value of userManager to set.
*/
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
/**
* {@inheritDoc}
*/
public void addFtpEventListener(FtpEventListener lstnr) {
this.ftpEventListeners.add(lstnr);
}
/**
* {@inheritDoc}
*/
public void downloadPerformed(String clientId, File file) {
for (FtpEventListener listener : ftpEventListeners) {
listener.downloadPerformed(clientId, file);
}
log.debug("Download event delegated to listeners.");
}
/**
* {@inheritDoc}
*/
public void loginPerformed(String clientId, boolean successful) {
for (FtpEventListener listener : ftpEventListeners) {
listener.loginPerformed(clientId, successful);
}
log.debug("Login event delegated to listeners.");
}
/**
* {@inheritDoc}
*/
public void uploadPerformed(String clientId, File file) {
for (FtpEventListener listener : ftpEventListeners) {
listener.uploadPerformed(clientId, file);
}
log.debug("Upload event delegated to listeners.");
}
/**
* {@inheritDoc}
*/
public void sessionOpened(Object sessionObj) {
for (FtpEventListener listener : ftpEventListeners) {
listener.sessionOpened(sessionObj);
}
log.debug("Session opened event delegated to listeners.");
}
/**
* {@inheritDoc}
*/
public void sessionClosed(Object sessionObj) {
synchronized (this) {
sessions.remove(sessionObj);
}
for (FtpEventListener listener : ftpEventListeners) {
listener.sessionOpened(sessionObj);
}
log.debug("Session closed event delegated to listeners.");
}
/**
* {@inheritDoc}
*/
public List<FtpSession> getSessions() {
return sessions;
}
/**
* {@inheritDoc}
*/
public int getConnectionCountHWMark() {
return connectionCountHWMark;
}
/**
* {@inheritDoc}
*/
public Date getConnectionCountHWMarkDate() {
return connectionCountHWMarkDate;
}
/**
* Setter methode for property <code>connectionCountHWMark</code>.
*
* @param connectionCountHWMark Value for <code>connectionCountHWMark</code>.
*/
public void setConnectionCountHWMark(int connectionCountHWMark) {
this.connectionCountHWMark = connectionCountHWMark;
}
/**
* Setter methode for property <code>connectionCountHWMarkDate</code>.
*
* @param connectionCountHWMarkDate Value for <code>connectionCountHWMarkDate</code>.
*/
public void setConnectionCountHWMarkDate(Date connectionCountHWMarkDate) {
this.connectionCountHWMarkDate = connectionCountHWMarkDate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}