Java tutorial
/* * ------------------------------------------------------------------------------ * 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.session.impl; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import com.apporiented.hermesftp.cmd.SocketProvider; 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.exception.FtpConfigException; import com.apporiented.hermesftp.exception.FtpQuotaException; import com.apporiented.hermesftp.usermanager.UserManager; import com.apporiented.hermesftp.usermanager.model.GroupDataList; import com.apporiented.hermesftp.usermanager.model.UserData; import com.apporiented.hermesftp.utils.LoggingReader; import com.apporiented.hermesftp.utils.LoggingWriter; import com.apporiented.hermesftp.utils.VarMerger; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class servers as a means of transportation for data shared by a single FTP session. * Instances of the <code>FtpSessionContextImpl</code> class are passed to each of the commands * while executing a FTP command sequence. The command objects read connection settings and other * options from the context. In turn data that may concern the general state of the FTP session can * be stored in the context. * * @author Lars Behnke */ public class FtpSessionContextImpl implements FtpConstants, FtpSessionContext { private static Log log = LogFactory.getLog(FtpSessionContextImpl.class); private static int portIdx; private String user; private String password; private boolean authenticated; private int dataType = DT_BINARY; private int transmissionMode = MODE_STREAM; private int storageStructure = STRUCT_FILE; private String remoteDir; private Socket clientSocket; private BufferedReader clientCmdReader; private PrintWriter clientResponseWriter; private FtpServerOptions options; private FtpEventListener eventListener; private ResourceBundle resourceBundle; private SocketProvider dataSocketProvider; private UserManager userManager; private Date creationTime; private Map<String, Object> attributes; private Map<String, Long> sessionStatistics = Collections.synchronizedMap(new HashMap<String, Long>()); /** * Constructor. * * @param options The server options. * @param userManager The user manager. * @param resourceBundle The resource bundle that containts messages and texts. * @param listener The listener that is informed on session events. */ public FtpSessionContextImpl(FtpServerOptions options, UserManager userManager, ResourceBundle resourceBundle, FtpEventListener listener) { super(); this.userManager = userManager; this.resourceBundle = resourceBundle; this.options = options; this.attributes = Collections.synchronizedMap(new HashMap<String, Object>()); this.eventListener = listener; } @Override public void check() throws FtpConfigException { try { String dirName = getOptions().getRootDir(); File dir = new File(dirName); if (!dir.exists() || !dir.isDirectory()) { throw new FtpConfigException("Remote directory not found."); } } catch (RuntimeException e) { throw new FtpConfigException("Invalid configuration: " + e); } } /** * {@inheritDoc} */ public Object getAttribute(String name) { return attributes.get(name); } /** * {@inheritDoc} */ public void setAttribute(String name, Object value) { if (value == null) { attributes.remove(name); } else { attributes.put(name, value); } } /** * {@inheritDoc} */ public FtpServerOptions getOptions() { return options; } /** * {@inheritDoc} */ public String getOption(String key) { return getOptions().getProperty(key); } /** * {@inheritDoc} */ public String getPassword() { return password; } /** * {@inheritDoc} */ public void setPassword(String password) { this.password = password; } /** * {@inheritDoc} */ public String getRemoteDir() { if (remoteDir == null) { remoteDir = getOptions().getRootDir(); } return remoteDir; } /** * {@inheritDoc} */ public String getRemoteRelDir() { String relDir = null; try { String canDir = new File(getRemoteDir()).getCanonicalPath(); canDir = FilenameUtils.normalizeNoEndSeparator(canDir); String canRoot = new File(getOptions().getRootDir()).getCanonicalPath(); canRoot = FilenameUtils.normalizeNoEndSeparator(canRoot); if (canDir.toUpperCase().startsWith(canRoot.toUpperCase())) { relDir = canDir.substring(canRoot.length()); if (!relDir.startsWith(File.separator)) { relDir = File.separator + relDir; } } else { relDir = canDir; } } catch (IOException e) { log.error(e); } return relDir; } /** * {@inheritDoc} */ public void setRemoteDir(String remoteDir) { this.remoteDir = FilenameUtils.normalizeNoEndSeparator(remoteDir); } /** * {@inheritDoc} */ public String getUser() { return user; } /** * {@inheritDoc} */ public void setUser(String user) { this.user = user; } /** * {@inheritDoc} */ public FtpEventListener getEventListener() { return eventListener; } /** * {@inheritDoc} */ public String getRes(String id) { return resourceBundle.getString(id); } /** * {@inheritDoc} */ public boolean isAuthenticated() { return authenticated; } /** * {@inheritDoc} */ public int getDataType() { return dataType; } /** * {@inheritDoc} */ public void setDataType(int dataType) { this.dataType = dataType; } /** * {@inheritDoc} */ public int getStorageStructure() { return storageStructure; } /** * {@inheritDoc} */ public void setStorageStructure(int storageStructure) { this.storageStructure = storageStructure; } /** * {@inheritDoc} */ public int getTransmissionMode() { return transmissionMode; } /** * {@inheritDoc} */ public void setTransmissionMode(int transmissionMode) { this.transmissionMode = transmissionMode; } /** * {@inheritDoc} */ public SocketProvider getDataSocketProvider() { return dataSocketProvider; } /** * {@inheritDoc} */ public void setDataSocketProvider(SocketProvider provider) { this.dataSocketProvider = provider; } /** * {@inheritDoc} */ public Socket getClientSocket() { return clientSocket; } /** * {@inheritDoc} */ public void setClientSocket(Socket clientSocket) throws IOException { this.clientSocket = clientSocket; this.clientResponseWriter = new LoggingWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true); this.clientCmdReader = new LoggingReader(new InputStreamReader(clientSocket.getInputStream())); } /** * {@inheritDoc} */ public PrintWriter getClientResponseWriter() { return clientResponseWriter; } /** * {@inheritDoc} */ public BufferedReader getClientCmdReader() { return clientCmdReader; } /** * {@inheritDoc} */ public int getPermission(String path) { int result = PRIV_NONE; try { GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA); result = list.getPermission(path, getUser(), options.getRootDir()); } catch (FtpConfigException e) { log.error(e); } return result; } /** * {@inheritDoc} */ public UserManager getUserManager() { return userManager; } /** * {@inheritDoc} */ public boolean authenticate() { authenticated = false; String dirName = null; try { authenticated = userManager.authenticate(getUser(), getPassword(), this); if (authenticated) { setAttribute(ATTR_LOGIN_TIME, new Date()); UserData userData = userManager.getUserData(getUser()); setAttribute(ATTR_USER_DATA, userData); GroupDataList groupList = userManager.getGroupDataList(getUser()); setAttribute(ATTR_GROUP_DATA, groupList); dirName = getStartDir(); File dir = new File(dirName); if (!dir.exists()) { FileUtils.forceMkdir(dir); } setRemoteDir(dirName); } } catch (FtpConfigException e) { log.error(e); } catch (IOException e) { log.error("Could not create directory: " + dirName); } return authenticated; } private synchronized String getStartDir() throws FtpConfigException { UserData userData = (UserData) getAttribute(ATTR_USER_DATA); if (userData == null) { throw new FtpConfigException("User data not available"); } VarMerger varMerger = new VarMerger(userData.getDir()); Properties props = new Properties(); props.setProperty("ftproot", FilenameUtils.separatorsToUnix(options.getRootDir())); props.setProperty("user", user); varMerger.merge(props); if (!varMerger.isReplacementComplete()) { throw new FtpConfigException("Unresolved placeholders in user configuration file found."); } return varMerger.getText(); } /** * {@inheritDoc} */ public UserData getUserData() { return (UserData) getAttribute(ATTR_USER_DATA); } /** * {@inheritDoc} */ public void resetCredentials() { authenticated = false; setUser(null); setPassword(null); } /** * {@inheritDoc} */ public void closeSockets() { if (getDataSocketProvider() != null) { getDataSocketProvider().closeSocket(); } } /** * {@inheritDoc} */ public String getCharset() { String charset; Boolean forceUtf8 = (Boolean) getAttribute(ATTR_FORCE_UTF8); if (forceUtf8 != null && forceUtf8) { charset = "UTF-8"; } else { String key = getDataType() == DT_EBCDIC ? OPT_CHARSET_EBCDIC : OPT_CHARSET_ASCII; charset = getOptions().getProperty(key); } return charset; } /** * {@inheritDoc} */ public Integer getNextPassivePort() { Integer port; Integer[] allowedPorts = getOptions().getAllowedPorts(); if (allowedPorts == null || allowedPorts.length == 0) { /* Let the system decide which port to use. */ port = 0; } else { /* Get the port from the user defined list. */ port = allowedPorts[portIdx++]; if (portIdx >= allowedPorts.length) { portIdx = 0; } } return port; } /** * {@inheritDoc} */ public Date getCreationTime() { return creationTime; } /** * {@inheritDoc} */ public void setCreationTime(Date creationTime) { this.creationTime = creationTime; } /** * {@inheritDoc} */ public Map<String, Long> getSessionStatistics() { return sessionStatistics; } private int getUpperLimit(String globalOptionKey, String groupLimitKey) { long result; long globalLimit = getOptions().getInt(globalOptionKey, -1); GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA); long groupLimit = list.getUpperLimit(groupLimitKey); if (globalLimit < 0) { result = groupLimit; } else if (groupLimit < 0) { result = globalLimit; } else { result = Math.max(groupLimit, globalLimit); } return (int) result; } /** * {@inheritDoc} */ public int getMaxDownloadRate() { return getUpperLimit(OPT_MAX_DOWNLOAD_RATE, STAT_DOWNLOAD_RATE); } /** * {@inheritDoc} */ public int getMaxUploadRate() { return getUpperLimit(OPT_MAX_UPLOAD_RATE, STAT_UPLOAD_RATE); } /** * Increases a particular resource consumption by the passed value. * * @param countKey The name of the statistic. * @param value The value * @throws FtpQuotaException Thrown if a resource limit has been reached. */ public void updateIncrementalStat(String countKey, long value) throws FtpQuotaException { /* All sessions of user */ getUserManager().updateIncrementalStatistics(getUser(), countKey, value); /* Current session */ Map<String, Long> sessionStats = getSessionStatistics(); Long consumptionObj = sessionStats.get(countKey); long consumption = consumptionObj == null ? 0 : consumptionObj; sessionStats.put(countKey, consumption + value); } /** * Updates the upload or download transfer rate taking the passed value into account. * * @param avgKey The name of the statistic. * @param value The value */ public void updateAverageStat(String avgKey, int value) { /* All sessions of user */ getUserManager().updateAverageStatistics(getUser(), avgKey, value); /* Current session */ String countKey = "Sample count (" + avgKey + ")"; Map<String, Long> sessionStats = getSessionStatistics(); Long prevAvgObj = sessionStats.get(avgKey); long prevAvg = prevAvgObj == null ? 0 : prevAvgObj; Long prevCountObj = sessionStats.get(countKey); long prevCount = prevCountObj == null ? 0 : prevCountObj; long currentAvg = (prevAvg * prevCount + value) / (prevCount + 1); sessionStats.put(avgKey, currentAvg); sessionStats.put(countKey, prevCount + 1); } }