blob: d2cd6e50e68fe65931f002195891a94a2699c1b9 [file] [log] [blame]
/*
* 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.impl;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.cert.Certificate;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import org.apache.ftpserver.ftplet.DataType;
import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.FtpReply;
import org.apache.ftpserver.ftplet.FtpSession;
import org.apache.ftpserver.ftplet.Structure;
import org.apache.ftpserver.ftplet.User;
import org.apache.ftpserver.listener.Listener;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.future.ReadFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.service.IoService;
import org.apache.mina.core.service.TransportMetadata;
import org.apache.mina.core.session.AbstractIoSession;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.session.IoSessionConfig;
import org.apache.mina.core.write.WriteRequest;
import org.apache.mina.core.write.WriteRequestQueue;
import org.apache.mina.filter.ssl.SslFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <strong>Internal class, do not use directly.</strong>
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*
*/
public class FtpIoSession implements IoSession {
/**
* Contains user name between USER and PASS commands
*/
public static final String ATTRIBUTE_PREFIX = "org.apache.ftpserver.";
private static final String ATTRIBUTE_USER_ARGUMENT = ATTRIBUTE_PREFIX
+ "user-argument";
private static final String ATTRIBUTE_SESSION_ID = ATTRIBUTE_PREFIX
+ "session-id";
private static final String ATTRIBUTE_USER = ATTRIBUTE_PREFIX + "user";
private static final String ATTRIBUTE_LANGUAGE = ATTRIBUTE_PREFIX
+ "language";
private static final String ATTRIBUTE_LOGIN_TIME = ATTRIBUTE_PREFIX
+ "login-time";
private static final String ATTRIBUTE_DATA_CONNECTION = ATTRIBUTE_PREFIX
+ "data-connection";
private static final String ATTRIBUTE_FILE_SYSTEM = ATTRIBUTE_PREFIX
+ "file-system";
private static final String ATTRIBUTE_RENAME_FROM = ATTRIBUTE_PREFIX
+ "rename-from";
private static final String ATTRIBUTE_FILE_OFFSET = ATTRIBUTE_PREFIX
+ "file-offset";
private static final String ATTRIBUTE_DATA_TYPE = ATTRIBUTE_PREFIX
+ "data-type";
private static final String ATTRIBUTE_STRUCTURE = ATTRIBUTE_PREFIX
+ "structure";
private static final String ATTRIBUTE_FAILED_LOGINS = ATTRIBUTE_PREFIX
+ "failed-logins";
private static final String ATTRIBUTE_LISTENER = ATTRIBUTE_PREFIX
+ "listener";
private static final String ATTRIBUTE_MAX_IDLE_TIME = ATTRIBUTE_PREFIX
+ "max-idle-time";
private static final String ATTRIBUTE_LAST_ACCESS_TIME = ATTRIBUTE_PREFIX
+ "last-access-time";
private static final String ATTRIBUTE_CACHED_REMOTE_ADDRESS = ATTRIBUTE_PREFIX
+ "cached-remote-address";
private final IoSession wrappedSession;
private final FtpServerContext context;
/**
* Last reply that was sent to the client, if any.
*/
private FtpReply lastReply = null;
/* Begin wrapped IoSession methods */
/**
* @see IoSession#close()
*/
public CloseFuture close() {
return wrappedSession.close();
}
/**
* @see IoSession#close(boolean)
*/
public CloseFuture close(boolean immediately) {
return wrappedSession.close(immediately);
}
/**
* @see IoSession#containsAttribute(Object)
*/
public boolean containsAttribute(Object key) {
return wrappedSession.containsAttribute(key);
}
/**
* @see IoSession#getAttachment()
*/
@SuppressWarnings("deprecation")
public Object getAttachment() {
return wrappedSession.getAttachment();
}
/**
* @see IoSession#getAttribute(Object)
*/
public Object getAttribute(Object key) {
return wrappedSession.getAttribute(key);
}
/**
* @see IoSession#getAttribute(Object, Object)
*/
public Object getAttribute(Object key, Object defaultValue) {
return wrappedSession.getAttribute(key, defaultValue);
}
/**
* @see IoSession#getAttributeKeys()
*/
public Set<Object> getAttributeKeys() {
return wrappedSession.getAttributeKeys();
}
/**
* @see IoSession#getBothIdleCount()
*/
public int getBothIdleCount() {
return wrappedSession.getBothIdleCount();
}
/**
* @see IoSession#getCloseFuture()
*/
public CloseFuture getCloseFuture() {
return wrappedSession.getCloseFuture();
}
/**
* @see IoSession#getConfig()
*/
public IoSessionConfig getConfig() {
return wrappedSession.getConfig();
}
/**
* @see IoSession#getCreationTime()
*/
public long getCreationTime() {
return wrappedSession.getCreationTime();
}
/**
* @see IoSession#getFilterChain()
*/
public IoFilterChain getFilterChain() {
return wrappedSession.getFilterChain();
}
/**
* @see IoSession#getHandler()
*/
public IoHandler getHandler() {
return wrappedSession.getHandler();
}
/**
* @see IoSession#getId()
*/
public long getId() {
return wrappedSession.getId();
}
/**
* @see IoSession#getIdleCount(IdleStatus)
*/
public int getIdleCount(IdleStatus status) {
return wrappedSession.getIdleCount(status);
}
/**
* @see IoSession#getLastBothIdleTime()
*/
public long getLastBothIdleTime() {
return wrappedSession.getLastBothIdleTime();
}
/**
* @see IoSession#getLastIdleTime(IdleStatus)
*/
public long getLastIdleTime(IdleStatus status) {
return wrappedSession.getLastIdleTime(status);
}
/**
* @see IoSession#getLastIoTime()
*/
public long getLastIoTime() {
return wrappedSession.getLastIoTime();
}
/**
* @see IoSession#getLastReadTime()
*/
public long getLastReadTime() {
return wrappedSession.getLastReadTime();
}
/**
* @see IoSession#getLastReaderIdleTime()
*/
public long getLastReaderIdleTime() {
return wrappedSession.getLastReaderIdleTime();
}
/**
* @see IoSession#getLastWriteTime()
*/
public long getLastWriteTime() {
return wrappedSession.getLastWriteTime();
}
/**
* @see IoSession#getLastWriterIdleTime()
*/
public long getLastWriterIdleTime() {
return wrappedSession.getLastWriterIdleTime();
}
/**
* @see IoSession#getLocalAddress()
*/
public SocketAddress getLocalAddress() {
return wrappedSession.getLocalAddress();
}
/**
* @see IoSession#getReadBytes()
*/
public long getReadBytes() {
return wrappedSession.getReadBytes();
}
/**
* @see IoSession#getReadBytesThroughput()
*/
public double getReadBytesThroughput() {
return wrappedSession.getReadBytesThroughput();
}
/**
* @see IoSession#getReadMessages()
*/
public long getReadMessages() {
return wrappedSession.getReadMessages();
}
/**
* @see IoSession#getReadMessagesThroughput()
*/
public double getReadMessagesThroughput() {
return wrappedSession.getReadMessagesThroughput();
}
/**
* @see IoSession#getReaderIdleCount()
*/
public int getReaderIdleCount() {
return wrappedSession.getReaderIdleCount();
}
/**
* @see IoSession#getRemoteAddress()
*/
public SocketAddress getRemoteAddress() {
// when closing a socket, the remote address might be reset to null
// therefore, we attempt to keep a cached copy around
SocketAddress address = wrappedSession.getRemoteAddress();
if (address == null
&& containsAttribute(ATTRIBUTE_CACHED_REMOTE_ADDRESS)) {
return (SocketAddress) getAttribute(ATTRIBUTE_CACHED_REMOTE_ADDRESS);
} else {
setAttribute(ATTRIBUTE_CACHED_REMOTE_ADDRESS, address);
return address;
}
}
/**
* @see IoSession#getScheduledWriteBytes()
*/
public long getScheduledWriteBytes() {
return wrappedSession.getScheduledWriteBytes();
}
/**
* @see IoSession#getScheduledWriteMessages()
*/
public int getScheduledWriteMessages() {
return wrappedSession.getScheduledWriteMessages();
}
/**
* @see IoSession#getService()
*/
public IoService getService() {
return wrappedSession.getService();
}
/**
* @see IoSession#getServiceAddress()
*/
public SocketAddress getServiceAddress() {
return wrappedSession.getServiceAddress();
}
/**
* @see IoSession#getTransportMetadata()
*/
public TransportMetadata getTransportMetadata() {
return wrappedSession.getTransportMetadata();
}
/**
* @see IoSession#getWriterIdleCount()
*/
public int getWriterIdleCount() {
return wrappedSession.getWriterIdleCount();
}
/**
* @see IoSession#getWrittenBytes()
*/
public long getWrittenBytes() {
return wrappedSession.getWrittenBytes();
}
/**
* @see IoSession#getWrittenBytesThroughput()
*/
public double getWrittenBytesThroughput() {
return wrappedSession.getWrittenBytesThroughput();
}
/**
* @see IoSession#getWrittenMessages()
*/
public long getWrittenMessages() {
return wrappedSession.getWrittenMessages();
}
/**
* @see IoSession#getWrittenMessagesThroughput()
*/
public double getWrittenMessagesThroughput() {
return wrappedSession.getWrittenMessagesThroughput();
}
/**
* @see IoSession#isClosing()
*/
public boolean isClosing() {
return wrappedSession.isClosing();
}
/**
* @see IoSession#isConnected()
*/
public boolean isConnected() {
return wrappedSession.isConnected();
}
/**
* @see IoSession#isIdle(IdleStatus)
*/
public boolean isIdle(IdleStatus status) {
return wrappedSession.isIdle(status);
}
/**
* @see IoSession#read()
*/
public ReadFuture read() {
return wrappedSession.read();
}
/**
* @see IoSession#removeAttribute(Object)
*/
public Object removeAttribute(Object key) {
return wrappedSession.removeAttribute(key);
}
/**
* @see IoSession#removeAttribute(Object, Object)
*/
public boolean removeAttribute(Object key, Object value) {
return wrappedSession.removeAttribute(key, value);
}
/**
* @see IoSession#replaceAttribute(Object, Object, Object)
*/
public boolean replaceAttribute(Object key, Object oldValue, Object newValue) {
return wrappedSession.replaceAttribute(key, oldValue, newValue);
}
/**
* @see IoSession#resumeRead()
*/
public void resumeRead() {
wrappedSession.resumeRead();
}
/**
* @see IoSession#resumeWrite()
*/
public void resumeWrite() {
wrappedSession.resumeWrite();
}
/**
* @see IoSession#setAttachment(Object)
*/
@SuppressWarnings("deprecation")
public Object setAttachment(Object attachment) {
return wrappedSession.setAttachment(attachment);
}
/**
* @see IoSession#setAttribute(Object)
*/
public Object setAttribute(Object key) {
return wrappedSession.setAttribute(key);
}
/**
* @see IoSession#setAttribute(Object, Object)
*/
public Object setAttribute(Object key, Object value) {
return wrappedSession.setAttribute(key, value);
}
/**
* @see IoSession#setAttributeIfAbsent(Object)
*/
public Object setAttributeIfAbsent(Object key) {
return wrappedSession.setAttributeIfAbsent(key);
}
/**
* @see IoSession#setAttributeIfAbsent(Object, Object)
*/
public Object setAttributeIfAbsent(Object key, Object value) {
return wrappedSession.setAttributeIfAbsent(key, value);
}
/**
* @see IoSession#suspendRead()
*/
public void suspendRead() {
wrappedSession.suspendRead();
}
/**
* @see IoSession#suspendWrite()
*/
public void suspendWrite() {
wrappedSession.suspendWrite();
}
/**
* @see IoSession#write(Object)
*/
public WriteFuture write(Object message) {
WriteFuture future = wrappedSession.write(message);
this.lastReply = (FtpReply) message;
return future;
}
/**
* @see IoSession#write(Object, SocketAddress)
*/
public WriteFuture write(Object message, SocketAddress destination) {
WriteFuture future = wrappedSession.write(message, destination);
this.lastReply = (FtpReply) message;
return future;
}
/* End wrapped IoSession methods */
public void resetState() {
removeAttribute(ATTRIBUTE_RENAME_FROM);
removeAttribute(ATTRIBUTE_FILE_OFFSET);
}
public synchronized ServerDataConnectionFactory getDataConnection() {
if (containsAttribute(ATTRIBUTE_DATA_CONNECTION)) {
return (ServerDataConnectionFactory) getAttribute(ATTRIBUTE_DATA_CONNECTION);
} else {
IODataConnectionFactory dataCon = new IODataConnectionFactory(
context, this);
dataCon.setServerControlAddress(((InetSocketAddress) getLocalAddress()).getAddress());
setAttribute(ATTRIBUTE_DATA_CONNECTION, dataCon);
return dataCon;
}
}
public FileSystemView getFileSystemView() {
return (FileSystemView) getAttribute(ATTRIBUTE_FILE_SYSTEM);
}
public User getUser() {
return (User) getAttribute(ATTRIBUTE_USER);
}
/**
* Is logged-in
*/
public boolean isLoggedIn() {
return containsAttribute(ATTRIBUTE_USER);
}
public Listener getListener() {
return (Listener) getAttribute(ATTRIBUTE_LISTENER);
}
public void setListener(Listener listener) {
setAttribute(ATTRIBUTE_LISTENER, listener);
}
public FtpSession getFtpletSession() {
return new DefaultFtpSession(this);
}
public String getLanguage() {
return (String) getAttribute(ATTRIBUTE_LANGUAGE);
}
public void setLanguage(String language) {
setAttribute(ATTRIBUTE_LANGUAGE, language);
}
public String getUserArgument() {
return (String) getAttribute(ATTRIBUTE_USER_ARGUMENT);
}
public void setUser(User user) {
setAttribute(ATTRIBUTE_USER, user);
}
public void setUserArgument(String userArgument) {
setAttribute(ATTRIBUTE_USER_ARGUMENT, userArgument);
}
public int getMaxIdleTime() {
return (Integer) getAttribute(ATTRIBUTE_MAX_IDLE_TIME, 0);
}
public void setMaxIdleTime(int maxIdleTime) {
setAttribute(ATTRIBUTE_MAX_IDLE_TIME, maxIdleTime);
int listenerTimeout = getListener().getIdleTimeout();
// the listener timeout should be the upper limit, unless set to unlimited
// if the user limit is set to be unlimited, use the listener value is the threshold
// (already used as the default for all sessions)
// else, if the user limit is less than the listener idle time, use the user limit
if (listenerTimeout <= 0
|| (maxIdleTime > 0 && maxIdleTime < listenerTimeout)) {
wrappedSession.getConfig().setBothIdleTime(maxIdleTime);
}
}
public synchronized void increaseFailedLogins() {
int failedLogins = (Integer) getAttribute(ATTRIBUTE_FAILED_LOGINS, 0);
failedLogins++;
setAttribute(ATTRIBUTE_FAILED_LOGINS, failedLogins);
}
public int getFailedLogins() {
return (Integer) getAttribute(ATTRIBUTE_FAILED_LOGINS, 0);
}
public void setLogin(FileSystemView fsview) {
setAttribute(ATTRIBUTE_LOGIN_TIME, new Date());
setAttribute(ATTRIBUTE_FILE_SYSTEM, fsview);
}
public void reinitialize() {
logoutUser();
removeAttribute(ATTRIBUTE_USER);
removeAttribute(ATTRIBUTE_USER_ARGUMENT);
removeAttribute(ATTRIBUTE_LOGIN_TIME);
removeAttribute(ATTRIBUTE_FILE_SYSTEM);
removeAttribute(ATTRIBUTE_RENAME_FROM);
removeAttribute(ATTRIBUTE_FILE_OFFSET);
}
public void logoutUser() {
ServerFtpStatistics stats = ((ServerFtpStatistics) context.getFtpStatistics());
if (stats != null) {
stats.setLogout(this);
LoggerFactory.getLogger(this.getClass()).debug("Statistics login decreased due to user logout");
} else {
LoggerFactory.getLogger(this.getClass()).warn("Statistics not available in session, can not decrease login count");
}
}
public void setFileOffset(long fileOffset) {
setAttribute(ATTRIBUTE_FILE_OFFSET, fileOffset);
}
public void setRenameFrom(FtpFile renFr) {
setAttribute(ATTRIBUTE_RENAME_FROM, renFr);
}
public FtpFile getRenameFrom() {
return (FtpFile) getAttribute(ATTRIBUTE_RENAME_FROM);
}
public long getFileOffset() {
return (Long) getAttribute(ATTRIBUTE_FILE_OFFSET, 0L);
}
public void setStructure(Structure structure) {
setAttribute(ATTRIBUTE_STRUCTURE, structure);
}
public void setDataType(DataType dataType) {
setAttribute(ATTRIBUTE_DATA_TYPE, dataType);
}
/**
* @see FtpSession#getSessionId()
*/
public UUID getSessionId() {
synchronized (wrappedSession) {
if (!wrappedSession.containsAttribute(ATTRIBUTE_SESSION_ID)) {
wrappedSession.setAttribute(ATTRIBUTE_SESSION_ID, UUID.randomUUID());
}
return (UUID) wrappedSession.getAttribute(ATTRIBUTE_SESSION_ID);
}
}
public FtpIoSession(IoSession wrappedSession, FtpServerContext context) {
this.wrappedSession = wrappedSession;
this.context = context;
}
public Structure getStructure() {
return (Structure) getAttribute(ATTRIBUTE_STRUCTURE, Structure.FILE);
}
public DataType getDataType() {
return (DataType) getAttribute(ATTRIBUTE_DATA_TYPE, DataType.ASCII);
}
public Date getLoginTime() {
return (Date) getAttribute(ATTRIBUTE_LOGIN_TIME);
}
public Date getLastAccessTime() {
return (Date) getAttribute(ATTRIBUTE_LAST_ACCESS_TIME);
}
public Certificate[] getClientCertificates() {
if (getFilterChain().contains(SslFilter.class)) {
SslFilter sslFilter = (SslFilter) getFilterChain().get(
SslFilter.class);
SSLSession sslSession = sslFilter.getSslSession(this);
if (sslSession != null) {
try {
return sslSession.getPeerCertificates();
} catch (SSLPeerUnverifiedException e) {
// ignore, certificate will not be available to the session
}
}
}
// no certificates available
return null;
}
public void updateLastAccessTime() {
setAttribute(ATTRIBUTE_LAST_ACCESS_TIME, new Date());
}
/**
* @see IoSession#getCurrentWriteMessage()
*/
public Object getCurrentWriteMessage() {
return wrappedSession.getCurrentWriteMessage();
}
/**
* @see IoSession#getCurrentWriteRequest()
*/
public WriteRequest getCurrentWriteRequest() {
return wrappedSession.getCurrentWriteRequest();
}
/**
* @see IoSession#isBothIdle()
*/
public boolean isBothIdle() {
return wrappedSession.isBothIdle();
}
/**
* @see IoSession#isReaderIdle()
*/
public boolean isReaderIdle() {
return wrappedSession.isReaderIdle();
}
/**
* @see IoSession#isWriterIdle()
*/
public boolean isWriterIdle() {
return wrappedSession.isWriterIdle();
}
/**
* Indicates whether the control socket for this session is secure, that is,
* running over SSL/TLS
*
* @return true if the control socket is secured
*/
public boolean isSecure() {
return getFilterChain().contains(SslFilter.class);
}
/**
* Increase the number of bytes written on the data connection
* @param increment The number of bytes written
*/
public void increaseWrittenDataBytes(int increment) {
if (wrappedSession instanceof AbstractIoSession) {
((AbstractIoSession) wrappedSession).increaseScheduledWriteBytes(increment);
((AbstractIoSession) wrappedSession).increaseWrittenBytes(
increment, System.currentTimeMillis());
}
}
/**
* Increase the number of bytes read on the data connection
* @param increment The number of bytes written
*/
public void increaseReadDataBytes(int increment) {
if (wrappedSession instanceof AbstractIoSession) {
((AbstractIoSession) wrappedSession).increaseReadBytes(increment,
System.currentTimeMillis());
}
}
/**
* Returns the last reply that was sent to the client.
* @return the last reply that was sent to the client.
*/
public FtpReply getLastReply() {
return lastReply;
}
/**
* @see IoSession#getWriteRequestQueue()
*/
public WriteRequestQueue getWriteRequestQueue() {
return wrappedSession.getWriteRequestQueue();
}
/**
* @see IoSession#isReadSuspended()
*/
public boolean isReadSuspended() {
return wrappedSession.isReadSuspended();
}
/**
* @see IoSession#isWriteSuspended()
*/
public boolean isWriteSuspended() {
return wrappedSession.isWriteSuspended();
}
/**
* @see IoSession#setCurrentWriteRequest(WriteRequest)
*/
public void setCurrentWriteRequest(WriteRequest currentWriteRequest) {
wrappedSession.setCurrentWriteRequest(currentWriteRequest);
}
/**
* @see IoSession#updateThroughput(long, boolean)
*/
public void updateThroughput(long currentTime, boolean force) {
wrappedSession.updateThroughput(currentTime, force);
}
}