mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-02 17:35:18 +00:00
were both beyond me, and are just the raw conflict merge data. If Kev can't figure out how they should go together by tomorrow AM (for me) I'll dig back in. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@4306 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
3751 lines
103 KiB
Java
3751 lines
103 KiB
Java
/*
|
|
* Copyright (C) 2005 Alfresco, Inc.
|
|
*
|
|
* Licensed under the Mozilla Public License version 1.1
|
|
* with a permitted attribution clause. You may obtain a
|
|
* copy of the License at
|
|
*
|
|
* http://www.alfresco.org/legal/license.txt
|
|
*
|
|
* 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.alfresco.filesys.ftp;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.Writer;
|
|
import java.net.InetAddress;
|
|
import java.net.Socket;
|
|
import java.net.SocketException;
|
|
import java.net.UnknownHostException;
|
|
import java.util.Date;
|
|
import java.util.Enumeration;
|
|
import java.util.StringTokenizer;
|
|
import java.util.Vector;
|
|
|
|
import javax.transaction.UserTransaction;
|
|
|
|
import org.alfresco.filesys.server.SrvSession;
|
|
import org.alfresco.filesys.server.auth.ClientInfo;
|
|
import org.alfresco.filesys.server.auth.acl.AccessControl;
|
|
import org.alfresco.filesys.server.auth.acl.AccessControlManager;
|
|
import org.alfresco.filesys.server.core.SharedDevice;
|
|
import org.alfresco.filesys.server.core.SharedDeviceList;
|
|
import org.alfresco.filesys.server.filesys.AccessDeniedException;
|
|
import org.alfresco.filesys.server.filesys.AccessMode;
|
|
import org.alfresco.filesys.server.filesys.DiskDeviceContext;
|
|
import org.alfresco.filesys.server.filesys.DiskFullException;
|
|
import org.alfresco.filesys.server.filesys.DiskInterface;
|
|
import org.alfresco.filesys.server.filesys.DiskSharedDevice;
|
|
import org.alfresco.filesys.server.filesys.FileAction;
|
|
import org.alfresco.filesys.server.filesys.FileAttribute;
|
|
import org.alfresco.filesys.server.filesys.FileInfo;
|
|
import org.alfresco.filesys.server.filesys.FileOpenParams;
|
|
import org.alfresco.filesys.server.filesys.FileStatus;
|
|
import org.alfresco.filesys.server.filesys.NetworkFile;
|
|
import org.alfresco.filesys.server.filesys.NotifyChange;
|
|
import org.alfresco.filesys.server.filesys.SearchContext;
|
|
import org.alfresco.filesys.server.filesys.SrvDiskInfo;
|
|
import org.alfresco.filesys.server.filesys.TreeConnection;
|
|
import org.alfresco.filesys.server.filesys.TreeConnectionHash;
|
|
import org.alfresco.filesys.smb.server.repo.ContentContext;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.cmr.security.PersonService;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* FTP Server Session Class
|
|
*
|
|
* @author GKSpencer
|
|
*/
|
|
public class FTPSrvSession extends SrvSession implements Runnable
|
|
{
|
|
|
|
// Debug logging
|
|
|
|
private static final Log logger = LogFactory.getLog("org.alfresco.ftp.protocol");
|
|
|
|
// Constants
|
|
//
|
|
// Debug flag values
|
|
|
|
public static final int DBG_STATE = 0x00000001; // Session state changes
|
|
|
|
public static final int DBG_SEARCH = 0x00000002; // File/directory search
|
|
|
|
public static final int DBG_INFO = 0x00000004; // Information requests
|
|
|
|
public static final int DBG_FILE = 0x00000008; // File open/close/info
|
|
|
|
public static final int DBG_FILEIO = 0x00000010; // File read/write
|
|
|
|
public static final int DBG_ERROR = 0x00000020; // Errors
|
|
|
|
public static final int DBG_PKTTYPE = 0x00000040; // Received packet type
|
|
|
|
public static final int DBG_TIMING = 0x00000080; // Time packet
|
|
|
|
// processing
|
|
|
|
public static final int DBG_DATAPORT = 0x00000100; // Data port
|
|
|
|
public static final int DBG_DIRECTORY = 0x00000200; // Directory commands
|
|
|
|
// Anonymous user name
|
|
|
|
private static final String USER_ANONYMOUS = "anonymous";
|
|
|
|
// Root directory and FTP directory seperator
|
|
|
|
private static final String ROOT_DIRECTORY = "/";
|
|
|
|
private static final String FTP_SEPERATOR = "/";
|
|
|
|
private static final char FTP_SEPERATOR_CHAR = '/';
|
|
|
|
// Share relative path directory seperator
|
|
|
|
private static final String DIR_SEPERATOR = "\\";
|
|
|
|
private static final char DIR_SEPERATOR_CHAR = '\\';
|
|
|
|
// File transfer buffer size
|
|
|
|
public static final int DEFAULT_BUFFERSIZE = 64000;
|
|
|
|
// Carriage return/line feed combination required for response messages
|
|
|
|
protected final static String CRLF = "\r\n";
|
|
|
|
// LIST command options
|
|
|
|
protected final static String LIST_OPTION_HIDDEN = "-a";
|
|
|
|
// Flag to control whether data transfers use a seperate thread
|
|
|
|
private static boolean UseThreadedDataTransfer = false;
|
|
|
|
// Session socket
|
|
|
|
private Socket m_sock;
|
|
|
|
// Input/output streams to remote client
|
|
|
|
private InputStreamReader m_in;
|
|
|
|
private char[] m_inbuf;
|
|
|
|
private OutputStreamWriter m_out;
|
|
|
|
private StringBuffer m_outbuf;
|
|
|
|
// Data connection
|
|
|
|
private FTPDataSession m_dataSess;
|
|
|
|
// Current working directory details
|
|
//
|
|
// First level is the share name then a path relative to the share root
|
|
|
|
private FTPPath m_cwd;
|
|
|
|
// Binary mode flag
|
|
|
|
private boolean m_binary = false;
|
|
|
|
// Restart position for binary file transfer
|
|
|
|
private long m_restartPos = 0;
|
|
|
|
// Rename from path details
|
|
|
|
private FTPPath m_renameFrom;
|
|
|
|
// Filtered list of shared filesystems available to this session
|
|
|
|
private SharedDeviceList m_shares;
|
|
|
|
// List of shared device connections used by this session
|
|
|
|
private TreeConnectionHash m_connections;
|
|
|
|
/**
|
|
* Class constructor
|
|
*
|
|
* @param sock
|
|
* Socket
|
|
* @param srv
|
|
* FTPServer
|
|
*/
|
|
public FTPSrvSession(Socket sock, FTPNetworkServer srv)
|
|
{
|
|
super(-1, srv, "FTP", null);
|
|
|
|
// Save the local socket
|
|
|
|
m_sock = sock;
|
|
|
|
// Set the socket linger options, so the socket closes immediately when
|
|
// closed
|
|
|
|
try
|
|
{
|
|
m_sock.setSoLinger(false, 0);
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
}
|
|
|
|
// Indicate that the user is not logged in
|
|
|
|
setLoggedOn(false);
|
|
|
|
// Allocate the FTP path
|
|
|
|
m_cwd = new FTPPath();
|
|
|
|
// Allocate the tree connection cache
|
|
|
|
m_connections = new TreeConnectionHash();
|
|
}
|
|
|
|
/**
|
|
* Close the FTP session, and associated data socket if active
|
|
*/
|
|
public final void closeSession()
|
|
{
|
|
|
|
// Call the base class
|
|
|
|
super.closeSession();
|
|
|
|
// Close the data connection, if active
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
// Abort any active transfer
|
|
|
|
m_dataSess.abortTransfer();
|
|
|
|
// Remove the data session
|
|
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
|
|
// Close the socket first, if the client is still connected this should
|
|
// allow the input/output streams to be closed
|
|
|
|
if (m_sock != null)
|
|
{
|
|
try
|
|
{
|
|
m_sock.close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
m_sock = null;
|
|
}
|
|
|
|
// Close the input/output streams
|
|
|
|
if (m_in != null)
|
|
{
|
|
try
|
|
{
|
|
m_in.close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
m_in = null;
|
|
}
|
|
|
|
if (m_out != null)
|
|
{
|
|
try
|
|
{
|
|
m_out.close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
m_out = null;
|
|
}
|
|
|
|
// Remove session from server session list
|
|
|
|
getFTPServer().removeSession(this);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("Session closed, " + getSessionId());
|
|
}
|
|
|
|
/**
|
|
* Return the current working directory
|
|
*
|
|
* @return String
|
|
*/
|
|
public final String getCurrentWorkingDirectory()
|
|
{
|
|
return m_cwd.getFTPPath();
|
|
}
|
|
|
|
/**
|
|
* Return the server that this session is associated with.
|
|
*
|
|
* @return FTPServer
|
|
*/
|
|
public final FTPNetworkServer getFTPServer()
|
|
{
|
|
return (FTPNetworkServer) getServer();
|
|
}
|
|
|
|
/**
|
|
* Return the client network address
|
|
*
|
|
* @return InetAddress
|
|
*/
|
|
public final InetAddress getRemoteAddress()
|
|
{
|
|
return m_sock.getInetAddress();
|
|
}
|
|
|
|
/**
|
|
* Check if there is a current working directory
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public final boolean hasCurrentWorkingDirectory()
|
|
{
|
|
return m_cwd != null ? true : false;
|
|
}
|
|
|
|
/**
|
|
* Set the default path for the session
|
|
*
|
|
* @param rootPath FTPPath
|
|
*/
|
|
public final void setRootPath(FTPPath rootPath)
|
|
{
|
|
|
|
// Initialize the current working directory using the root path
|
|
|
|
m_cwd = new FTPPath(rootPath);
|
|
if ( rootPath.hasSharedDevice())
|
|
m_cwd.setSharedDevice( rootPath.getSharedDevice());
|
|
else
|
|
m_cwd.setSharedDevice(getShareList(), this);
|
|
}
|
|
|
|
/**
|
|
* Get the path details for the current request
|
|
*
|
|
* @param req FTPRequest
|
|
* @param filePath boolean
|
|
* @return FTPPath
|
|
*/
|
|
protected final FTPPath generatePathForRequest(FTPRequest req, boolean filePath)
|
|
{
|
|
return generatePathForRequest(req, filePath, true);
|
|
}
|
|
|
|
/**
|
|
* Get the path details for the current request
|
|
*
|
|
* @param req FTPRequest
|
|
* @param filePath boolean
|
|
* @param checkExists boolean
|
|
* @return FTPPath
|
|
*/
|
|
protected final FTPPath generatePathForRequest(FTPRequest req, boolean filePath, boolean checkExists)
|
|
{
|
|
// Use the global share list for normal connections and the per session list for guest access
|
|
|
|
SharedDeviceList shareList = null;
|
|
|
|
if ( getClientInformation().isGuest())
|
|
shareList = getDynamicShareList();
|
|
else
|
|
shareList = getShareList();
|
|
|
|
// Convert the path to an FTP format path
|
|
|
|
String path = convertToFTPSeperators(req.getArgument());
|
|
|
|
// Check if the path is the root directory and there is a default root
|
|
// path configured
|
|
|
|
FTPPath ftpPath = null;
|
|
|
|
if (path.compareTo(ROOT_DIRECTORY) == 0)
|
|
{
|
|
|
|
// Check if the FTP server has a default root directory configured
|
|
|
|
FTPNetworkServer ftpSrv = (FTPNetworkServer) getServer();
|
|
if (ftpSrv.hasRootPath())
|
|
ftpPath = ftpSrv.getRootPath();
|
|
else
|
|
{
|
|
try
|
|
{
|
|
ftpPath = new FTPPath("/");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
return ftpPath;
|
|
}
|
|
}
|
|
|
|
// Check if the path is relative
|
|
|
|
else if (FTPPath.isRelativePath(path) == false)
|
|
{
|
|
|
|
// Create a new path for the directory
|
|
|
|
try
|
|
{
|
|
ftpPath = new FTPPath(path);
|
|
}
|
|
catch (InvalidPathException ex)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Find the associated shared device
|
|
|
|
if (ftpPath.setSharedDevice( shareList, this) == false)
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Check for the special '.' directory, just return the current
|
|
// working directory
|
|
|
|
if (path.equals("."))
|
|
return m_cwd;
|
|
|
|
// Check for the special '..' directory, if already at the root
|
|
// directory return an
|
|
// error
|
|
|
|
if (path.equals(".."))
|
|
{
|
|
|
|
// Check if we are already at the root path
|
|
|
|
if (m_cwd.isRootPath() == false)
|
|
{
|
|
|
|
// Remove the last directory from the path
|
|
|
|
m_cwd.removeDirectory();
|
|
m_cwd.setSharedDevice( shareList, this);
|
|
return m_cwd;
|
|
}
|
|
else
|
|
return null;
|
|
}
|
|
|
|
// Create a copy of the current working directory and append the new
|
|
// file/directory name
|
|
|
|
ftpPath = new FTPPath(m_cwd);
|
|
|
|
// Check if the root directory/share has been set
|
|
|
|
if (ftpPath.isRootPath())
|
|
{
|
|
|
|
// Path specifies the share name
|
|
|
|
try
|
|
{
|
|
ftpPath.setSharePath(path, null);
|
|
}
|
|
catch (InvalidPathException ex)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (filePath)
|
|
ftpPath.addFile(path);
|
|
else
|
|
ftpPath.addDirectory(path);
|
|
}
|
|
|
|
// Find the associated shared device, if not already set
|
|
|
|
if (ftpPath.hasSharedDevice() == false && ftpPath.setSharedDevice( shareList, this) == false)
|
|
return null;
|
|
}
|
|
|
|
// Check if the generated path exists
|
|
|
|
if (checkExists)
|
|
{
|
|
|
|
// Check if the new path exists and is a directory
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Access the virtual filesystem driver
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
|
|
// Check if the path exists
|
|
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.NotExist)
|
|
{
|
|
|
|
// Get the path string, check if there is a leading
|
|
// seperator
|
|
|
|
String pathStr = req.getArgument();
|
|
if (pathStr.startsWith(FTP_SEPERATOR) == false)
|
|
pathStr = FTP_SEPERATOR + pathStr;
|
|
|
|
// Create the root path
|
|
|
|
ftpPath = new FTPPath(pathStr);
|
|
|
|
// Find the associated shared device
|
|
|
|
if (ftpPath.setSharedDevice(getShareList(), this) == false)
|
|
ftpPath = null;
|
|
else
|
|
{
|
|
// Recheck if the path exists
|
|
|
|
sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
if ( sts == FileStatus.NotExist)
|
|
ftpPath = null;
|
|
}
|
|
}
|
|
else if ((sts == FileStatus.FileExists && filePath == false)
|
|
|| (sts == FileStatus.DirectoryExists && filePath == true))
|
|
{
|
|
|
|
// Path exists but is the wrong type (directory or file)
|
|
|
|
ftpPath = null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// DEBUG
|
|
|
|
if ( logger.isErrorEnabled())
|
|
logger.error("Error generating FTP path", ex);
|
|
|
|
ftpPath = null;
|
|
}
|
|
}
|
|
|
|
// Return the new path
|
|
|
|
return ftpPath;
|
|
}
|
|
|
|
/**
|
|
* Convert a path string from share path seperators to FTP path seperators
|
|
*
|
|
* @param path String
|
|
* @return String
|
|
*/
|
|
protected final String convertToFTPSeperators(String path)
|
|
{
|
|
|
|
// Check if the path is valid
|
|
|
|
if (path == null || path.indexOf(DIR_SEPERATOR) == -1)
|
|
return path;
|
|
|
|
// Replace the path seperators
|
|
|
|
return path.replace(DIR_SEPERATOR_CHAR, FTP_SEPERATOR_CHAR);
|
|
}
|
|
|
|
/**
|
|
* Find the required disk shared device
|
|
*
|
|
* @param name String
|
|
* @return DiskSharedDevice
|
|
*/
|
|
protected final DiskSharedDevice findShare(String name)
|
|
{
|
|
|
|
// Check if the name is valid
|
|
|
|
if (name == null)
|
|
return null;
|
|
|
|
// Find the required disk share
|
|
|
|
SharedDevice shr = getFTPServer().getShareList().findShare(m_cwd.getShareName());
|
|
|
|
if (shr != null && shr instanceof DiskSharedDevice)
|
|
return (DiskSharedDevice) shr;
|
|
|
|
// Disk share not found
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Set the binary mode flag
|
|
*
|
|
* @param bin boolean
|
|
*/
|
|
protected final void setBinary(boolean bin)
|
|
{
|
|
m_binary = bin;
|
|
}
|
|
|
|
/**
|
|
* Send an FTP command response
|
|
*
|
|
* @param stsCode int
|
|
* @param msg String
|
|
* @exception IOException
|
|
*/
|
|
protected final void sendFTPResponse(int stsCode, String msg) throws IOException
|
|
{
|
|
|
|
// Build the output record
|
|
|
|
m_outbuf.setLength(0);
|
|
m_outbuf.append(stsCode);
|
|
m_outbuf.append(" ");
|
|
|
|
if (msg != null)
|
|
m_outbuf.append(msg);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR) && stsCode >= 500)
|
|
logger.debug("Error status=" + stsCode + ", msg=" + msg);
|
|
|
|
// Add the CR/LF
|
|
|
|
m_outbuf.append(CRLF);
|
|
|
|
// Output the FTP response
|
|
|
|
if (m_out != null)
|
|
{
|
|
m_out.write(m_outbuf.toString());
|
|
m_out.flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send an FTP command response
|
|
*
|
|
* @param msg StringBuffer
|
|
* @exception IOException
|
|
*/
|
|
protected final void sendFTPResponse(StringBuffer msg) throws IOException
|
|
{
|
|
|
|
// Output the FTP response
|
|
|
|
if (m_out != null)
|
|
{
|
|
m_out.write(msg.toString());
|
|
m_out.write(CRLF);
|
|
m_out.flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send an FTP command response
|
|
*
|
|
* @param msg String
|
|
* @exception IOException
|
|
*/
|
|
protected final void sendFTPResponse(String msg) throws IOException
|
|
{
|
|
|
|
// Output the FTP response
|
|
|
|
if (m_out != null)
|
|
{
|
|
m_out.write(msg);
|
|
m_out.write(CRLF);
|
|
m_out.flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a user command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procUser(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Clear the current client information
|
|
|
|
setClientInformation(null);
|
|
setLoggedOn(false);
|
|
|
|
// Check if a user name has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error in parameters or arguments");
|
|
return;
|
|
}
|
|
|
|
// Check for an anonymous login
|
|
|
|
if (getFTPServer().allowAnonymous() == true
|
|
&& req.getArgument().equalsIgnoreCase(getFTPServer().getAnonymousAccount()))
|
|
{
|
|
|
|
// Anonymous login, create guest client information
|
|
|
|
ClientInfo cinfo = new ClientInfo(getFTPServer().getAnonymousAccount(), null);
|
|
cinfo.setGuest(true);
|
|
setClientInformation(cinfo);
|
|
|
|
// Return the anonymous login response
|
|
|
|
sendFTPResponse(331, "Guest login ok, send your complete e-mail address as password");
|
|
return;
|
|
}
|
|
|
|
// Create client information for the user
|
|
|
|
setClientInformation(new ClientInfo(req.getArgument(), null));
|
|
|
|
// Valid user, wait for the password
|
|
|
|
sendFTPResponse(331, "User name okay, need password for " + req.getArgument());
|
|
}
|
|
|
|
/**
|
|
* Process a password command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procPassword(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the client information has been set, this indicates a user
|
|
// command has been received
|
|
|
|
if (hasClientInformation() == false)
|
|
{
|
|
sendFTPResponse(500, "Syntax error, command "
|
|
+ FTPCommand.getCommandName(req.isCommand()) + " unrecognized");
|
|
return;
|
|
}
|
|
|
|
// Check for an anonymous login, accept any password string
|
|
|
|
ClientInfo cInfo = getClientInformation();
|
|
|
|
if (cInfo.isGuest())
|
|
{
|
|
if ( getFTPServer().allowAnonymous() == true)
|
|
{
|
|
// Authenticate as the guest user
|
|
|
|
AuthenticationComponent authComponent = getServer().getConfiguration().getAuthenticationComponent();
|
|
cInfo.setUserName( authComponent.getGuestUserName());
|
|
}
|
|
else
|
|
{
|
|
// Return an access denied error
|
|
|
|
sendFTPResponse(530, "Access denied");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("Anonymous logon not allowed");
|
|
|
|
// Close the connection
|
|
|
|
closeSession();
|
|
}
|
|
}
|
|
|
|
// Get the client information and store the received plain text password
|
|
|
|
cInfo.setPassword(req.getArgument());
|
|
|
|
// Use the normal authentication service as we have the plaintext password
|
|
|
|
AuthenticationService authService = getServer().getConfiguration().getAuthenticationService();
|
|
|
|
try
|
|
{
|
|
// Authenticate the user
|
|
|
|
if ( cInfo.isGuest())
|
|
{
|
|
// Authenticate as the guest user
|
|
|
|
authService.authenticateAsGuest();
|
|
}
|
|
else
|
|
{
|
|
// Authenticate as a normal user
|
|
|
|
authService.authenticate( cInfo.getUserName(), cInfo.getPasswordAsCharArray());
|
|
}
|
|
|
|
// User successfully logged on
|
|
|
|
sendFTPResponse(230, "User logged in, proceed");
|
|
setLoggedOn(true);
|
|
|
|
// Save the client info
|
|
|
|
setClientInformation( cInfo);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("User " + getClientInformation().getUserName() + ", logon successful");
|
|
}
|
|
catch (org.alfresco.repo.security.authentication.AuthenticationException ex)
|
|
{
|
|
// DEBUG
|
|
|
|
if ( logger.isDebugEnabled())
|
|
logger.debug("Logon failed for user " + cInfo.getUserName());
|
|
}
|
|
|
|
// Check if the logon was successful
|
|
|
|
if ( isLoggedOn() == true)
|
|
{
|
|
// If the user has successfully logged on to the FTP server then inform listeners
|
|
|
|
getFTPServer().sessionLoggedOn(this);
|
|
|
|
// If this is a guest logon then we need to set the root folder to the guest user home folder
|
|
// as guest is not allowed access to other areas
|
|
|
|
if ( cInfo.isGuest())
|
|
{
|
|
// Generate a dynamic share with the guest users home folder as the root
|
|
|
|
DiskSharedDevice guestShare = createHomeDiskShare( cInfo);
|
|
addDynamicShare( guestShare);
|
|
|
|
// Set the root path for the guest logon to the guest share
|
|
|
|
StringBuilder rootPath = new StringBuilder();
|
|
|
|
rootPath.append(FTP_SEPERATOR);
|
|
rootPath.append( guestShare.getName());
|
|
rootPath.append(FTP_SEPERATOR);
|
|
|
|
FTPPath guestRoot = null;
|
|
|
|
try
|
|
{
|
|
// Set the root path for this FTP session
|
|
|
|
guestRoot = new FTPPath( rootPath.toString());
|
|
guestRoot.setSharedDevice( guestShare);
|
|
setRootPath( guestRoot);
|
|
}
|
|
catch ( InvalidPathException ex)
|
|
{
|
|
if ( logger.isErrorEnabled())
|
|
logger.error("Error setting guest FTP root path", ex);
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug(" Using root path " + guestRoot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Return an access denied error
|
|
|
|
sendFTPResponse(530, "Access denied");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("User " + getClientInformation().getUserName() + ", logon failed");
|
|
|
|
// Close the connection
|
|
|
|
closeSession();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a port command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procPort(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if the parameter has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Required argument missing");
|
|
return;
|
|
}
|
|
|
|
// Parse the address/port string into a IP address and port
|
|
|
|
StringTokenizer token = new StringTokenizer(req.getArgument(), ",");
|
|
if (token.countTokens() != 6)
|
|
{
|
|
sendFTPResponse(501, "Invalid argument");
|
|
return;
|
|
}
|
|
|
|
// Parse the client address
|
|
|
|
String addrStr = token.nextToken()
|
|
+ "." + token.nextToken() + "." + token.nextToken() + "." + token.nextToken();
|
|
InetAddress addr = null;
|
|
|
|
try
|
|
{
|
|
addr = InetAddress.getByName(addrStr);
|
|
}
|
|
catch (UnknownHostException ex)
|
|
{
|
|
sendFTPResponse(501, "Invalid argument (address)");
|
|
return;
|
|
}
|
|
|
|
// Parse the client port
|
|
|
|
int port = -1;
|
|
|
|
try
|
|
{
|
|
port = Integer.parseInt(token.nextToken()) * 256;
|
|
port += Integer.parseInt(token.nextToken());
|
|
}
|
|
catch (NumberFormatException ex)
|
|
{
|
|
sendFTPResponse(501, "Invalid argument (port)");
|
|
return;
|
|
}
|
|
|
|
// Create an active data session, the actual socket connection will be
|
|
// made later
|
|
|
|
m_dataSess = getFTPServer().allocateDataSession(this, addr, port);
|
|
|
|
// Return a success response to the client
|
|
|
|
sendFTPResponse(200, "Port OK");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DATAPORT))
|
|
logger.debug("Port open addr=" + addr + ", port=" + port);
|
|
}
|
|
|
|
/**
|
|
* Process a passive command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procPassive(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Create a passive data session
|
|
|
|
try
|
|
{
|
|
m_dataSess = getFTPServer().allocateDataSession(this, null, 0);
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
m_dataSess = null;
|
|
}
|
|
|
|
// Check if the data session is valid
|
|
|
|
if (m_dataSess == null)
|
|
{
|
|
sendFTPResponse(550, "Requested action not taken");
|
|
return;
|
|
}
|
|
|
|
// Get the passive connection address/port and return to the client
|
|
|
|
int pasvPort = m_dataSess.getPassivePort();
|
|
|
|
StringBuffer msg = new StringBuffer();
|
|
|
|
msg.append("227 Entering Passive Mode (");
|
|
msg.append(getFTPServer().getLocalFTPAddressString());
|
|
msg.append(",");
|
|
msg.append(pasvPort >> 8);
|
|
msg.append(",");
|
|
msg.append(pasvPort & 0xFF);
|
|
msg.append(")");
|
|
|
|
sendFTPResponse(msg);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DATAPORT))
|
|
logger.debug("Passive open addr=" + getFTPServer().getLocalFTPAddressString() + ", port=" + pasvPort);
|
|
}
|
|
|
|
/**
|
|
* Process a print working directory command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procPrintWorkDir(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Return the current working directory virtual path
|
|
|
|
sendFTPResponse(257, "\"" + m_cwd.getFTPPath() + "\"");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DIRECTORY))
|
|
logger.debug("Pwd ftp="
|
|
+ m_cwd.getFTPPath() + ", share=" + m_cwd.getShareName() + ", path=" + m_cwd.getSharePath());
|
|
}
|
|
|
|
/**
|
|
* Process a change working directory command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procChangeWorkDir(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if the request has a valid argument
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Path not specified");
|
|
return;
|
|
}
|
|
|
|
// Create the new working directory path
|
|
|
|
FTPPath newPath = generatePathForRequest(req, false);
|
|
if (newPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path " + req.getArgument());
|
|
return;
|
|
}
|
|
|
|
// Set the new current working directory
|
|
|
|
m_cwd = newPath;
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(250, "Requested file action OK");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DIRECTORY))
|
|
logger.debug("Cwd ftp="
|
|
+ m_cwd.getFTPPath() + ", share=" + m_cwd.getShareName() + ", path=" + m_cwd.getSharePath());
|
|
}
|
|
|
|
/**
|
|
* Process a change directory up command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procCdup(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if there is a current working directory path
|
|
|
|
if (m_cwd.isRootPath())
|
|
{
|
|
|
|
// Already at the root directory, return an error status
|
|
|
|
sendFTPResponse(550, "Already at root directory");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Remove the last directory from the path
|
|
|
|
m_cwd.removeDirectory();
|
|
if (m_cwd.isRootPath() == false && m_cwd.getSharedDevice() == null)
|
|
m_cwd.setSharedDevice(getShareList(), this);
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(250, "Requested file action OK");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DIRECTORY))
|
|
logger.debug("Cdup ftp="
|
|
+ m_cwd.getFTPPath() + ", share=" + m_cwd.getShareName() + ", path=" + m_cwd.getSharePath());
|
|
}
|
|
|
|
/**
|
|
* Process a long directory listing command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procList(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if the client has requested hidden files, via the '-a' option
|
|
|
|
boolean hidden = false;
|
|
|
|
if (req.hasArgument() && req.getArgument().startsWith(LIST_OPTION_HIDDEN))
|
|
{
|
|
// Indicate that we want hidden files in the listing
|
|
|
|
hidden = true;
|
|
|
|
// Remove the option from the command argument, and update the
|
|
// request
|
|
|
|
String arg = req.getArgument();
|
|
int pos = arg.indexOf(" ");
|
|
if (pos > 0)
|
|
arg = arg.substring(pos + 1);
|
|
else
|
|
arg = null;
|
|
|
|
req.updateArgument(arg);
|
|
}
|
|
|
|
// Create the path for the file listing
|
|
|
|
FTPPath ftpPath = m_cwd;
|
|
if ( req.hasArgument())
|
|
ftpPath = generatePathForRequest(req, true);
|
|
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(500, "Invalid path");
|
|
return;
|
|
}
|
|
|
|
// Check if the session has the required access
|
|
|
|
if (ftpPath.isRootPath() == false)
|
|
{
|
|
|
|
// Check if the session has access to the filesystem
|
|
|
|
TreeConnection tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
if (tree == null || tree.hasReadAccess() == false)
|
|
{
|
|
|
|
// Session does not have access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Send the intermediate response
|
|
|
|
sendFTPResponse(150, "File status okay, about to open data connection");
|
|
|
|
// Check if there is an active data session
|
|
|
|
if (m_dataSess == null)
|
|
{
|
|
sendFTPResponse(425, "Can't open data connection");
|
|
return;
|
|
}
|
|
|
|
// Get the data connection socket
|
|
|
|
Socket dataSock = null;
|
|
|
|
try
|
|
{
|
|
dataSock = m_dataSess.getSocket();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.debug(ex);
|
|
}
|
|
|
|
if (dataSock == null)
|
|
{
|
|
sendFTPResponse(426, "Connection closed; transfer aborted");
|
|
return;
|
|
}
|
|
|
|
// Output the directory listing to the client
|
|
|
|
Writer dataWrt = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Open an output stream to the client
|
|
|
|
dataWrt = new OutputStreamWriter(dataSock.getOutputStream());
|
|
|
|
// Check if a path has been specified to list
|
|
|
|
Vector<FileInfo> files = null;
|
|
|
|
if (req.hasArgument())
|
|
{
|
|
}
|
|
|
|
// Get a list of file information objects for the current directory
|
|
|
|
files = listFilesForPath(ftpPath, false, hidden);
|
|
|
|
// Output the file list to the client
|
|
|
|
if (files != null)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_SEARCH))
|
|
logger.debug("List found " + files.size() + " files in " + ftpPath.getFTPPath());
|
|
|
|
// Output the file information to the client
|
|
|
|
StringBuilder str = new StringBuilder(256);
|
|
|
|
for (FileInfo finfo : files)
|
|
{
|
|
// Build the output record
|
|
|
|
str.setLength(0);
|
|
|
|
str.append(finfo.isDirectory() ? "d" : "-");
|
|
str.append("rw-rw-rw- 1 user group ");
|
|
str.append(finfo.getSize());
|
|
str.append(" ");
|
|
|
|
FTPDate.packUnixDate(str, new Date(finfo.getModifyDateTime()));
|
|
|
|
str.append(" ");
|
|
str.append(finfo.getFileName());
|
|
str.append(CRLF);
|
|
|
|
// Output the file information record
|
|
|
|
dataWrt.write(str.toString());
|
|
}
|
|
|
|
// Flush the data stream
|
|
|
|
dataWrt.flush();
|
|
}
|
|
|
|
// Close the data stream and socket
|
|
|
|
dataWrt.close();
|
|
dataWrt = null;
|
|
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
|
|
// End of file list transmission
|
|
|
|
sendFTPResponse(226, "Closing data connection");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
// Failed to send file listing
|
|
|
|
sendFTPResponse(451, "Error reading file list");
|
|
} finally
|
|
{
|
|
|
|
// Close the data stream to the client
|
|
|
|
if (dataWrt != null)
|
|
dataWrt.close();
|
|
|
|
// Close the data connection to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a short directory listing command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procNList(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the file listing
|
|
|
|
FTPPath ftpPath = m_cwd;
|
|
if (req.hasArgument())
|
|
ftpPath = generatePathForRequest(req, true);
|
|
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(500, "Invalid path");
|
|
return;
|
|
}
|
|
|
|
// Check if the session has the required access
|
|
|
|
if (ftpPath.isRootPath() == false)
|
|
{
|
|
|
|
// Check if the session has access to the filesystem
|
|
|
|
TreeConnection tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
if (tree == null || tree.hasReadAccess() == false)
|
|
{
|
|
|
|
// Session does not have access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Send the intermediate response
|
|
|
|
sendFTPResponse(150, "File status okay, about to open data connection");
|
|
|
|
// Check if there is an active data session
|
|
|
|
if (m_dataSess == null)
|
|
{
|
|
sendFTPResponse(425, "Can't open data connection");
|
|
return;
|
|
}
|
|
|
|
// Get the data connection socket
|
|
|
|
Socket dataSock = null;
|
|
|
|
try
|
|
{
|
|
dataSock = m_dataSess.getSocket();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.error("Data socket error", ex);
|
|
}
|
|
|
|
if (dataSock == null)
|
|
{
|
|
sendFTPResponse(426, "Connection closed; transfer aborted");
|
|
return;
|
|
}
|
|
|
|
// Output the directory listing to the client
|
|
|
|
Writer dataWrt = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Open an output stream to the client
|
|
|
|
dataWrt = new OutputStreamWriter(dataSock.getOutputStream());
|
|
|
|
// Check if a path has been specified to list
|
|
|
|
Vector<FileInfo> files = null;
|
|
|
|
if (req.hasArgument())
|
|
{
|
|
}
|
|
|
|
// Get a list of file information objects for the current directory
|
|
|
|
files = listFilesForPath(ftpPath, false, false);
|
|
|
|
// Output the file list to the client
|
|
|
|
if (files != null)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_SEARCH))
|
|
logger.debug("List found " + files.size() + " files in " + ftpPath.getFTPPath());
|
|
|
|
// Output the file information to the client
|
|
|
|
for (FileInfo finfo : files)
|
|
{
|
|
|
|
// Output the file information record
|
|
|
|
dataWrt.write(finfo.getFileName());
|
|
dataWrt.write(CRLF);
|
|
}
|
|
}
|
|
|
|
// End of file list transmission
|
|
|
|
sendFTPResponse(226, "Closing data connection");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
// Failed to send file listing
|
|
|
|
sendFTPResponse(451, "Error reading file list");
|
|
} finally
|
|
{
|
|
|
|
// Close the data stream to the client
|
|
|
|
if (dataWrt != null)
|
|
dataWrt.close();
|
|
|
|
// Close the data connection to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a system status command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procSystemStatus(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return the system type
|
|
|
|
sendFTPResponse(215, "UNIX Type: Java FTP Server");
|
|
}
|
|
|
|
/**
|
|
* Process a server status command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procServerStatus(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return server status information
|
|
|
|
sendFTPResponse(211, "JLAN Server - Java FTP Server");
|
|
}
|
|
|
|
/**
|
|
* Process a help command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procHelp(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return help information
|
|
|
|
sendFTPResponse(211, "HELP text");
|
|
}
|
|
|
|
/**
|
|
* Process a no-op command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procNoop(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return a response
|
|
|
|
sendFTPResponse(200, "");
|
|
}
|
|
|
|
/**
|
|
* Process a quit command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procQuit(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return a response
|
|
|
|
sendFTPResponse(221, "Bye");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("Quit closing connection(s) to client");
|
|
|
|
// Close the session(s) to the client
|
|
|
|
closeSession();
|
|
}
|
|
|
|
/**
|
|
* Process a type command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procType(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Check if ASCII or binary mode is enabled
|
|
|
|
String arg = req.getArgument().toUpperCase();
|
|
if (arg.startsWith("A"))
|
|
setBinary(false);
|
|
else if (arg.startsWith("I") || arg.startsWith("L"))
|
|
setBinary(true);
|
|
else
|
|
{
|
|
|
|
// Invalid argument
|
|
|
|
sendFTPResponse(501, "Syntax error, invalid parameter");
|
|
return;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(200, "Command OK");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("Type arg=" + req.getArgument() + ", binary=" + m_binary);
|
|
}
|
|
|
|
/**
|
|
* Process a restart command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procRestart(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Validate the restart position
|
|
|
|
try
|
|
{
|
|
m_restartPos = Integer.parseInt(req.getArgument());
|
|
}
|
|
catch (NumberFormatException ex)
|
|
{
|
|
sendFTPResponse(501, "Invalid restart position");
|
|
return;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(350, "Restart OK");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILEIO))
|
|
logger.debug("Restart pos=" + m_restartPos);
|
|
}
|
|
|
|
/**
|
|
* Process a return file command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procReturnFile(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the file listing
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, true);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(500, "Invalid path");
|
|
return;
|
|
}
|
|
|
|
// Check if the path is the root directory
|
|
|
|
if (ftpPath.isRootPath() || ftpPath.isRootSharePath())
|
|
{
|
|
sendFTPResponse(550, "That is a directory");
|
|
return;
|
|
}
|
|
|
|
// Send the intermediate response
|
|
|
|
sendFTPResponse(150, "Connection accepted");
|
|
|
|
// Check if there is an active data session
|
|
|
|
if (m_dataSess == null)
|
|
{
|
|
sendFTPResponse(425, "Can't open data connection");
|
|
return;
|
|
}
|
|
|
|
// Check if a seperate thread should be used for the data transfer
|
|
|
|
if ( UseThreadedDataTransfer == true)
|
|
{
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("Returning (threaded) ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path=" + ftpPath.getSharePath());
|
|
|
|
// Start the transfer in a seperate thread
|
|
|
|
m_dataSess.doReturnFile( ftpPath, m_restartPos, req.getArgument());
|
|
}
|
|
else
|
|
{
|
|
// Get the data connection socket
|
|
|
|
Socket dataSock = null;
|
|
|
|
try
|
|
{
|
|
dataSock = m_dataSess.getSocket();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
|
|
if (dataSock == null)
|
|
{
|
|
sendFTPResponse(426, "Connection closed; transfer aborted");
|
|
return;
|
|
}
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("Returning ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path=" + ftpPath.getSharePath());
|
|
|
|
// Send the file to the client
|
|
|
|
OutputStream os = null;
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
NetworkFile netFile = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Open an output stream to the client
|
|
|
|
os = dataSock.getOutputStream();
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the file exists and it is a file, if so then open the
|
|
// file
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
|
|
// Create the file open parameters
|
|
|
|
FileOpenParams params = new FileOpenParams(ftpPath.getSharePath(), FileAction.OpenIfExists,
|
|
AccessMode.ReadOnly, 0);
|
|
|
|
// Check if the file exists and it is a file
|
|
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.FileExists)
|
|
{
|
|
|
|
// Open the file
|
|
|
|
netFile = disk.openFile(this, tree, params);
|
|
}
|
|
|
|
// Commit any current transaction
|
|
|
|
try
|
|
{
|
|
// Commit or rollback the transaction
|
|
|
|
endTransaction();
|
|
}
|
|
catch ( Exception ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled())
|
|
logger.debug("Error committing transaction", ex);
|
|
}
|
|
|
|
// Check if the file has been opened
|
|
|
|
if (netFile == null)
|
|
{
|
|
sendFTPResponse(550, "File " + req.getArgument() + " not available");
|
|
return;
|
|
}
|
|
|
|
// Allocate the buffer for the file data
|
|
|
|
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
|
|
long filePos = m_restartPos;
|
|
|
|
int len = -1;
|
|
|
|
while (filePos < netFile.getFileSize())
|
|
{
|
|
|
|
// Read another block of data from the file
|
|
|
|
len = disk.readFile(this, tree, netFile, buf, 0, buf.length, filePos);
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILEIO))
|
|
logger.debug(" Write len=" + len + " bytes");
|
|
|
|
// Write the current data block to the client, update the file
|
|
// position
|
|
|
|
if (len > 0)
|
|
{
|
|
|
|
// Write the data to the client
|
|
|
|
os.write(buf, 0, len);
|
|
|
|
// Update the file position
|
|
|
|
filePos += len;
|
|
}
|
|
}
|
|
|
|
// Close the output stream to the client
|
|
|
|
os.close();
|
|
os = null;
|
|
|
|
// Indicate that the file has been transmitted
|
|
|
|
sendFTPResponse(226, "Closing data connection");
|
|
|
|
// Close the data session
|
|
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
|
|
// Close the network file
|
|
|
|
disk.closeFile(this, tree, netFile);
|
|
netFile = null;
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILEIO))
|
|
logger.debug(" Transfer complete, file closed");
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Error during transfer", ex);
|
|
|
|
// Close the data socket to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
m_dataSess.closeSession();
|
|
m_dataSess = null;
|
|
}
|
|
|
|
// Indicate that there was an error during transmission of the file
|
|
// data
|
|
|
|
sendFTPResponse(426, "Data connection closed by client");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Error during transfer", ex);
|
|
|
|
// Indicate that there was an error during transmission of the file
|
|
// data
|
|
|
|
sendFTPResponse(426, "Error during transmission");
|
|
}
|
|
finally
|
|
{
|
|
|
|
// Close the network file
|
|
|
|
if (netFile != null && disk != null && tree != null)
|
|
disk.closeFile(this, tree, netFile);
|
|
|
|
// Close the output stream to the client
|
|
|
|
if (os != null)
|
|
os.close();
|
|
|
|
// Close the data connection to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a store file command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procStoreFile(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the file listing
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, true, false);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(500, "Invalid path");
|
|
return;
|
|
}
|
|
|
|
// Check if a seperate thread should be used for the data transfer
|
|
|
|
if ( UseThreadedDataTransfer == true)
|
|
{
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("Storing (threaded) ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
|
|
// Start the transfer in a seperate thread
|
|
|
|
m_dataSess.doStoreFile( ftpPath, m_restartPos, req.getArgument());
|
|
}
|
|
else
|
|
{
|
|
// Send the file to the client
|
|
|
|
InputStream is = null;
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
NetworkFile netFile = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.DirectoryExists)
|
|
{
|
|
|
|
// Return an error status
|
|
|
|
sendFTPResponse(500, "Invalid path (existing directory)");
|
|
return;
|
|
}
|
|
|
|
// Create the file open parameters
|
|
|
|
FileOpenParams params = new FileOpenParams(ftpPath.getSharePath(),
|
|
sts == FileStatus.FileExists ? FileAction.TruncateExisting : FileAction.CreateNotExist,
|
|
AccessMode.ReadWrite, 0);
|
|
|
|
// Create a new file to receive the data
|
|
|
|
if (sts == FileStatus.FileExists)
|
|
{
|
|
|
|
// Overwrite the existing file
|
|
|
|
netFile = disk.openFile(this, tree, params);
|
|
}
|
|
else
|
|
{
|
|
|
|
// Create a new file
|
|
|
|
netFile = disk.createFile(this, tree, params);
|
|
}
|
|
|
|
// Commit any current transaction
|
|
|
|
try
|
|
{
|
|
// Commit or rollback the transaction
|
|
|
|
endTransaction();
|
|
}
|
|
catch ( Exception ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled())
|
|
logger.debug("Error committing transaction", ex);
|
|
}
|
|
|
|
// Notify change listeners that a new file has been created
|
|
|
|
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
|
|
|
|
if (diskCtx.hasChangeHandler())
|
|
diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, ftpPath.getSharePath());
|
|
|
|
// Send the intermediate response
|
|
|
|
sendFTPResponse(150, "File status okay, about to open data connection");
|
|
|
|
// Check if there is an active data session
|
|
|
|
if (m_dataSess == null)
|
|
{
|
|
sendFTPResponse(425, "Can't open data connection");
|
|
return;
|
|
}
|
|
|
|
// Get the data connection socket
|
|
|
|
Socket dataSock = null;
|
|
|
|
try
|
|
{
|
|
dataSock = m_dataSess.getSocket();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
|
|
if (dataSock == null)
|
|
{
|
|
sendFTPResponse(426, "Connection closed; transfer aborted");
|
|
return;
|
|
}
|
|
|
|
// Open an input stream from the client
|
|
|
|
is = dataSock.getInputStream();
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("Storing ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
|
|
// Allocate the buffer for the file data
|
|
|
|
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
|
|
long filePos = 0;
|
|
int len = is.read(buf, 0, buf.length);
|
|
|
|
while (len > 0)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILEIO))
|
|
logger.debug(" Receive len=" + len + " bytes");
|
|
|
|
// Write the current data block to the file, update the file
|
|
// position
|
|
|
|
disk.writeFile(this, tree, netFile, buf, 0, len, filePos);
|
|
filePos += len;
|
|
|
|
// Read another block of data from the client
|
|
|
|
len = is.read(buf, 0, buf.length);
|
|
}
|
|
|
|
// Close the input stream from the client
|
|
|
|
is.close();
|
|
is = null;
|
|
|
|
// Close the network file
|
|
|
|
disk.closeFile(this, tree, netFile);
|
|
netFile = null;
|
|
|
|
// Indicate that the file has been received
|
|
|
|
sendFTPResponse(226, "Closing data connection");
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILEIO))
|
|
logger.debug(" Transfer complete, file closed");
|
|
}
|
|
catch( AccessDeniedException ex)
|
|
{
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Access denied", ex);
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Error during transfer", ex);
|
|
|
|
// Close the data socket to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
|
|
// Indicate that there was an error during transmission of the file
|
|
// data
|
|
|
|
sendFTPResponse(426, "Data connection closed by client");
|
|
}
|
|
catch (DiskFullException ex)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Error during transfer", ex);
|
|
|
|
// Close the data socket to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
|
|
// Indicate that there was an error during writing of the file
|
|
|
|
sendFTPResponse(451, "Disk full");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Error during transfer", ex);
|
|
|
|
// Indicate that there was an error during transmission of the file
|
|
// data
|
|
|
|
sendFTPResponse(426, "Error during transmission");
|
|
}
|
|
finally
|
|
{
|
|
|
|
// Close the network file
|
|
|
|
if (netFile != null && disk != null && tree != null)
|
|
disk.closeFile(this, tree, netFile);
|
|
|
|
// Close the input stream to the client
|
|
|
|
if (is != null)
|
|
is.close();
|
|
|
|
// Close the data connection to the client
|
|
|
|
if (m_dataSess != null)
|
|
{
|
|
getFTPServer().releaseDataSession(m_dataSess);
|
|
m_dataSess = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a delete file command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procDeleteFile(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the file
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, true);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path specified");
|
|
return;
|
|
}
|
|
|
|
// Delete the specified file
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists and it is a file
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.FileExists)
|
|
{
|
|
|
|
// Delete the file
|
|
|
|
disk.deleteFile(this, tree, ftpPath.getSharePath());
|
|
|
|
// Check if there are any file/directory change notify requests
|
|
// active
|
|
|
|
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
|
|
if (diskCtx.hasChangeHandler())
|
|
diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, ftpPath.getSharePath());
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("Deleted ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
}
|
|
else
|
|
{
|
|
|
|
// File does not exist or is a directory
|
|
|
|
sendFTPResponse(550, "File "
|
|
+ req.getArgument() + (sts == FileStatus.NotExist ? " not available" : " is a directory"));
|
|
return;
|
|
}
|
|
}
|
|
catch (AccessDeniedException ex)
|
|
{
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_ERROR))
|
|
logger.debug(" Access denied", ex);
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(450, "File action not taken");
|
|
return;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(250, "File " + req.getArgument() + " deleted");
|
|
}
|
|
|
|
/**
|
|
* Process a rename from command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procRenameFrom(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Clear the current rename from path details, if any
|
|
|
|
m_renameFrom = null;
|
|
|
|
// Create the path for the file/directory
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, false, false);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path specified");
|
|
return;
|
|
}
|
|
|
|
// Check that the file exists, and it is a file
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists and it is a file
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts != FileStatus.NotExist)
|
|
{
|
|
|
|
// Save the rename from file details, rename to command should
|
|
// follow
|
|
|
|
m_renameFrom = ftpPath;
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("RenameFrom ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
}
|
|
else
|
|
{
|
|
|
|
// File/directory does not exist
|
|
|
|
sendFTPResponse(550, "File "
|
|
+ req.getArgument() + (sts == FileStatus.NotExist ? " not available" : " is a directory"));
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(450, "File action not taken");
|
|
return;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(350, "File " + req.getArgument() + " OK");
|
|
}
|
|
|
|
/**
|
|
* Process a rename to command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procRenameTo(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Check if the rename from has already been set
|
|
|
|
if (m_renameFrom == null)
|
|
{
|
|
sendFTPResponse(550, "Rename from not set");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the new file name
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, true, false);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path specified");
|
|
return;
|
|
}
|
|
|
|
// Check that the rename is on the same share
|
|
|
|
if (m_renameFrom.getShareName().compareTo(ftpPath.getShareName()) != 0)
|
|
{
|
|
sendFTPResponse(550, "Cannot rename across shares");
|
|
return;
|
|
}
|
|
|
|
// Rename the file
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists and it is a file
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.NotExist)
|
|
{
|
|
|
|
// Rename the file/directory
|
|
|
|
disk.renameFile(this, tree, m_renameFrom.getSharePath(), ftpPath.getSharePath());
|
|
|
|
// Check if there are any file/directory change notify requests
|
|
// active
|
|
|
|
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
|
|
if (diskCtx.hasChangeHandler())
|
|
diskCtx.getChangeHandler().notifyRename(m_renameFrom.getSharePath(), ftpPath.getSharePath());
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("RenameTo ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
}
|
|
else
|
|
{
|
|
|
|
// File does not exist or is a directory
|
|
|
|
sendFTPResponse(550, "File "
|
|
+ req.getArgument() + (sts == FileStatus.NotExist ? " not available" : " is a directory"));
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(450, "File action not taken");
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
|
|
// Clear the rename details
|
|
|
|
m_renameFrom = null;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(250, "File renamed OK");
|
|
}
|
|
|
|
/**
|
|
* Process a create directory command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procCreateDirectory(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Check if the new directory contains multiple directories
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, false, false);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path " + req.getArgument());
|
|
return;
|
|
}
|
|
|
|
// Create the new directory
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the directory exists
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.NotExist)
|
|
{
|
|
|
|
// Create the new directory
|
|
|
|
FileOpenParams params = new FileOpenParams(ftpPath.getSharePath(), FileAction.CreateNotExist,
|
|
AccessMode.ReadWrite, FileAttribute.NTDirectory);
|
|
|
|
disk.createDirectory(this, tree, params);
|
|
|
|
// Notify change listeners that a new directory has been created
|
|
|
|
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
|
|
|
|
if (diskCtx.hasChangeHandler())
|
|
diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, ftpPath.getSharePath());
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DIRECTORY))
|
|
logger.debug("CreateDir ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
}
|
|
else
|
|
{
|
|
|
|
// File/directory already exists with that name, return an error
|
|
|
|
sendFTPResponse(450, sts == FileStatus.FileExists ? "File exists with that name"
|
|
: "Directory already exists");
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(450, "Failed to create directory");
|
|
return;
|
|
}
|
|
|
|
// Return the FTP path to the client
|
|
|
|
sendFTPResponse(250, ftpPath.getFTPPath());
|
|
}
|
|
|
|
/**
|
|
* Process a delete directory command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procRemoveDirectory(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Check if the directory path contains multiple directories
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, false);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path " + req.getArgument());
|
|
return;
|
|
}
|
|
|
|
// Check if the path is the root directory, cannot delete directories
|
|
// from the root
|
|
// directory
|
|
// as it maps to the list of available disk shares.
|
|
|
|
if (ftpPath.isRootPath() || ftpPath.isRootSharePath())
|
|
{
|
|
sendFTPResponse(550, "Access denied, cannot delete directory in root");
|
|
return;
|
|
}
|
|
|
|
// Delete the directory
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Check if the session has the required access to the filesystem
|
|
|
|
if (tree == null || tree.hasWriteAccess() == false)
|
|
{
|
|
|
|
// Session does not have write access to the filesystem
|
|
|
|
sendFTPResponse(550, "Access denied");
|
|
return;
|
|
}
|
|
|
|
// Check if the directory exists
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
int sts = disk.fileExists(this, tree, ftpPath.getSharePath());
|
|
|
|
if (sts == FileStatus.DirectoryExists)
|
|
{
|
|
|
|
// Delete the new directory
|
|
|
|
disk.deleteDirectory(this, tree, ftpPath.getSharePath());
|
|
|
|
// Check if there are any file/directory change notify requests
|
|
// active
|
|
|
|
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
|
|
if (diskCtx.hasChangeHandler())
|
|
diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, ftpPath.getSharePath());
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_DIRECTORY))
|
|
logger.debug("DeleteDir ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", path="
|
|
+ ftpPath.getSharePath());
|
|
}
|
|
else
|
|
{
|
|
|
|
// File already exists with that name or directory does not
|
|
// exist return an error
|
|
|
|
sendFTPResponse(550, sts == FileStatus.FileExists ? "File exists with that name"
|
|
: "Directory does not exist");
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(550, "Failed to delete directory");
|
|
return;
|
|
}
|
|
|
|
// Return a success status
|
|
|
|
sendFTPResponse(250, "Directory deleted OK");
|
|
}
|
|
|
|
/**
|
|
* Process a modify date/time command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procModifyDateTime(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return a success response
|
|
|
|
sendFTPResponse(550, "Not implemented yet");
|
|
}
|
|
|
|
/**
|
|
* Process a features command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procFeatures(FTPRequest req) throws IOException
|
|
{
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Send back the list of features supported by this FTP server
|
|
|
|
sendFTPResponse( 211, "Features");
|
|
sendFTPResponse( "SIZE");
|
|
sendFTPResponse( 211, "End");
|
|
}
|
|
|
|
/**
|
|
* Process a file size command
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procFileSize(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check if the user is logged in
|
|
|
|
if (isLoggedOn() == false)
|
|
{
|
|
sendFTPResponse(500, "");
|
|
return;
|
|
}
|
|
|
|
// Check if an argument has been specified
|
|
|
|
if (req.hasArgument() == false)
|
|
{
|
|
sendFTPResponse(501, "Syntax error, parameter required");
|
|
return;
|
|
}
|
|
|
|
// Create the path for the file listing
|
|
|
|
FTPPath ftpPath = generatePathForRequest(req, true);
|
|
if (ftpPath == null)
|
|
{
|
|
sendFTPResponse(550, "Invalid path");
|
|
return;
|
|
}
|
|
|
|
// Get the file information
|
|
|
|
DiskInterface disk = null;
|
|
TreeConnection tree = null;
|
|
|
|
try
|
|
{
|
|
|
|
// Create a temporary tree connection
|
|
|
|
tree = getTreeConnection(ftpPath.getSharedDevice());
|
|
|
|
// Access the virtual filesystem driver
|
|
|
|
disk = (DiskInterface) ftpPath.getSharedDevice().getInterface();
|
|
|
|
// Get the file information
|
|
|
|
FileInfo finfo = disk.getFileInformation(this, tree, ftpPath.getSharePath());
|
|
|
|
if (finfo == null)
|
|
{
|
|
sendFTPResponse(550, "File " + req.getArgument() + " not available");
|
|
return;
|
|
}
|
|
|
|
// Return the file size
|
|
|
|
sendFTPResponse(213, "" + finfo.getSize());
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_FILE))
|
|
logger.debug("File size ftp="
|
|
+ ftpPath.getFTPPath() + ", share=" + ftpPath.getShareName() + ", size=" + finfo.getSize());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sendFTPResponse(550, "Error retrieving file size");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a structure command. This command is obsolete.
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procStructure(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check for the file structure argument
|
|
|
|
if (req.hasArgument() && req.getArgument().equalsIgnoreCase("F"))
|
|
sendFTPResponse(200, "OK");
|
|
|
|
// Return an error response
|
|
|
|
sendFTPResponse(504, "Obsolete");
|
|
}
|
|
|
|
/**
|
|
* Process a mode command. This command is obsolete.
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procMode(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Check for the stream transfer mode argument
|
|
|
|
if (req.hasArgument() && req.getArgument().equalsIgnoreCase("S"))
|
|
sendFTPResponse(200, "OK");
|
|
|
|
// Return an error response
|
|
|
|
sendFTPResponse(504, "Obsolete");
|
|
}
|
|
|
|
/**
|
|
* Abort an active file transfer
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procAbort(FTPRequest req) throws IOException
|
|
{
|
|
// Check if threaded transfers are enabled
|
|
|
|
if ( UseThreadedDataTransfer == true)
|
|
{
|
|
// Check if there is an active data connection
|
|
|
|
if ( m_dataSess != null)
|
|
{
|
|
// Abort the data transfer
|
|
|
|
m_dataSess.abortTransfer();
|
|
}
|
|
else
|
|
{
|
|
// Send an error status, no transfer in progress
|
|
|
|
sendFTPResponse( 226, "Data connection not active");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Abort not implemented for inline transfers
|
|
|
|
sendFTPResponse( 502, "Abort not implemented");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process an allocate command. This command is obsolete.
|
|
*
|
|
* @param req FTPRequest
|
|
* @exception IOException
|
|
*/
|
|
protected final void procAllocate(FTPRequest req) throws IOException
|
|
{
|
|
|
|
// Return a response
|
|
|
|
sendFTPResponse(202, "Obsolete");
|
|
}
|
|
|
|
/**
|
|
* Build a list of file name or file information objects for the specified
|
|
* server path
|
|
*
|
|
* @param path FTPPath
|
|
* @param nameOnly boolean
|
|
* @param hidden boolean
|
|
* @return Vector<FileInfo>
|
|
*/
|
|
protected final Vector<FileInfo> listFilesForPath(FTPPath path, boolean nameOnly, boolean hidden)
|
|
{
|
|
|
|
// Check if the path is valid
|
|
|
|
if (path == null)
|
|
return null;
|
|
|
|
// Check if the path is the root path
|
|
|
|
Vector<FileInfo> files = new Vector<FileInfo>();
|
|
|
|
if (path.hasSharedDevice() == false)
|
|
{
|
|
|
|
// The first level of directories are mapped to the available shares
|
|
// Guest users only see their own list of shares
|
|
|
|
SharedDeviceList shares = null;
|
|
if ( getClientInformation().isGuest())
|
|
shares = getDynamicShareList();
|
|
else
|
|
shares = getShareList();
|
|
|
|
if (shares != null)
|
|
{
|
|
|
|
// Search for disk shares
|
|
|
|
Enumeration<SharedDevice> enm = shares.enumerateShares();
|
|
|
|
while (enm.hasMoreElements())
|
|
{
|
|
|
|
// Get the current shared device
|
|
|
|
SharedDevice shr = enm.nextElement();
|
|
|
|
// Add the share name or full information to the list
|
|
|
|
if (nameOnly == false)
|
|
{
|
|
|
|
// Create a file information object for the top level
|
|
// directory details
|
|
|
|
FileInfo finfo = new FileInfo(shr.getName(), 0L, FileAttribute.Directory);
|
|
files.add(finfo);
|
|
}
|
|
else
|
|
files.add(new FileInfo(shr.getName(), 0L, FileAttribute.Directory));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Append a wildcard to the search path
|
|
|
|
String searchPath = path.getSharePath();
|
|
|
|
if (path.isDirectory())
|
|
searchPath = path.makeSharePathToFile("*");
|
|
|
|
// Create a temporary tree connection
|
|
|
|
TreeConnection tree = new TreeConnection(path.getSharedDevice());
|
|
|
|
// Start a search on the specified disk share
|
|
|
|
DiskInterface disk = null;
|
|
SearchContext ctx = null;
|
|
|
|
int searchAttr = FileAttribute.Directory + FileAttribute.Normal;
|
|
if (hidden)
|
|
searchAttr += FileAttribute.Hidden;
|
|
|
|
try
|
|
{
|
|
disk = (DiskInterface) path.getSharedDevice().getInterface();
|
|
ctx = disk.startSearch(this, tree, searchPath, searchAttr);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
|
|
// Add the files to the list
|
|
|
|
if (ctx != null)
|
|
{
|
|
|
|
// Get the file names/information
|
|
|
|
while (ctx.hasMoreFiles())
|
|
{
|
|
|
|
// Check if a file name or file information is required
|
|
|
|
if (nameOnly)
|
|
{
|
|
|
|
// Add a file name to the list
|
|
|
|
files.add(new FileInfo(ctx.nextFileName(), 0L, 0));
|
|
}
|
|
else
|
|
{
|
|
|
|
// Create a file information object
|
|
|
|
FileInfo finfo = new FileInfo();
|
|
|
|
if (ctx.nextFileInfo(finfo) == false)
|
|
break;
|
|
if (finfo.getFileName() != null)
|
|
files.add(finfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the list of file names/information
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Get the list of filtered shares that are available to this session
|
|
*
|
|
* @return SharedDeviceList
|
|
*/
|
|
protected final SharedDeviceList getShareList()
|
|
{
|
|
|
|
// Check if the filtered share list has been initialized
|
|
|
|
if (m_shares == null)
|
|
{
|
|
|
|
// Get a list of shared filesystems
|
|
|
|
SharedDeviceList shares = getFTPServer().getShareMapper().getShareList(getFTPServer().getServerName(),
|
|
this, false);
|
|
|
|
// Search for disk shares
|
|
|
|
m_shares = new SharedDeviceList();
|
|
Enumeration enm = shares.enumerateShares();
|
|
|
|
while (enm.hasMoreElements())
|
|
{
|
|
|
|
// Get the current shared device
|
|
|
|
SharedDevice shr = (SharedDevice) enm.nextElement();
|
|
|
|
// Check if the share is a disk share
|
|
|
|
if (shr instanceof DiskSharedDevice)
|
|
m_shares.addShare(shr);
|
|
}
|
|
|
|
// Check if there is an access control manager available, if so then
|
|
// filter the list of
|
|
// shared filesystems
|
|
|
|
if (getServer().hasAccessControlManager())
|
|
{
|
|
|
|
// Get the access control manager
|
|
|
|
AccessControlManager aclMgr = getServer().getAccessControlManager();
|
|
|
|
// Filter the list of shared filesystems
|
|
|
|
m_shares = aclMgr.filterShareList(this, m_shares);
|
|
}
|
|
}
|
|
|
|
// Return the filtered shared filesystem list
|
|
|
|
return m_shares;
|
|
}
|
|
|
|
/**
|
|
* Get a tree connection for the specified shared device. Creates and caches
|
|
* a new tree connection if required.
|
|
*
|
|
* @param share SharedDevice
|
|
* @return TreeConnection
|
|
*/
|
|
protected final TreeConnection getTreeConnection(SharedDevice share)
|
|
{
|
|
|
|
// Check if the share is valid
|
|
|
|
if (share == null)
|
|
return null;
|
|
|
|
// Check if there is a tree connection in the cache
|
|
|
|
TreeConnection tree = m_connections.findConnection(share.getName());
|
|
if (tree == null)
|
|
{
|
|
|
|
// Create a new tree connection, do not add dynamic shares to the connection cache
|
|
|
|
tree = new TreeConnection(share);
|
|
if ( share.isTemporary() == false)
|
|
m_connections.addConnection(tree);
|
|
|
|
// Set the access permission for the shared filesystem
|
|
|
|
if (getServer().hasAccessControlManager())
|
|
{
|
|
|
|
// Set the access permission to the shared filesystem
|
|
|
|
AccessControlManager aclMgr = getServer().getAccessControlManager();
|
|
|
|
int access = aclMgr.checkAccessControl(this, share);
|
|
if (access != AccessControl.Default)
|
|
tree.setPermission(access);
|
|
}
|
|
}
|
|
|
|
// Return the connection
|
|
|
|
return tree;
|
|
}
|
|
|
|
/**
|
|
* Create a disk share for the home folder
|
|
*
|
|
* @param client ClientInfo
|
|
* @return DiskSharedDevice
|
|
*/
|
|
private final DiskSharedDevice createHomeDiskShare(ClientInfo client)
|
|
{
|
|
// Check if the home folder has been set for the user
|
|
|
|
if ( client.hasHomeFolder() == false)
|
|
{
|
|
// Get the required services
|
|
|
|
NodeService nodeService = getServer().getConfiguration().getNodeService();
|
|
PersonService personService = getServer().getConfiguration().getPersonService();
|
|
TransactionService transService = getServer().getConfiguration().getTransactionService();
|
|
|
|
// Get the home folder for the user
|
|
|
|
UserTransaction tx = transService.getUserTransaction();
|
|
NodeRef homeSpaceRef = null;
|
|
|
|
try
|
|
{
|
|
tx.begin();
|
|
homeSpaceRef = (NodeRef) nodeService.getProperty( personService.getPerson(client.getUserName()),
|
|
ContentModel.PROP_HOMEFOLDER);
|
|
client.setHomeFolder( homeSpaceRef);
|
|
tx.commit();
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
try
|
|
{
|
|
tx.rollback();
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
logger.error("Failed to rollback transaction", ex2);
|
|
}
|
|
|
|
if(ex instanceof RuntimeException)
|
|
{
|
|
throw (RuntimeException)ex;
|
|
}
|
|
else
|
|
{
|
|
throw new RuntimeException("Failed to get home folder", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the disk driver and context
|
|
|
|
DiskInterface diskDrv = getServer().getConfiguration().getDiskInterface();
|
|
DiskDeviceContext diskCtx = new ContentContext("", "", client.getHomeFolder());
|
|
|
|
// Default the filesystem to look like an 80Gb sized disk with 90% free space
|
|
|
|
diskCtx.setDiskInformation(new SrvDiskInfo(2560, 64, 512, 2304));
|
|
|
|
// Create a temporary shared device for the users home directory
|
|
|
|
return new DiskSharedDevice( client.getUserName(), diskDrv, diskCtx, SharedDevice.Temporary);
|
|
}
|
|
|
|
/**
|
|
* Start the FTP session in a seperate thread
|
|
*/
|
|
public void run()
|
|
{
|
|
|
|
try
|
|
{
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_STATE))
|
|
logger.debug("FTP session started");
|
|
|
|
// Create the input/output streams
|
|
|
|
m_in = new InputStreamReader(m_sock.getInputStream());
|
|
m_out = new OutputStreamWriter(m_sock.getOutputStream());
|
|
|
|
m_inbuf = new char[512];
|
|
m_outbuf = new StringBuffer(256);
|
|
|
|
// Return the initial response
|
|
|
|
sendFTPResponse(220, "FTP server ready");
|
|
|
|
// Start/end times if timing debug is enabled
|
|
|
|
long startTime = 0L;
|
|
long endTime = 0L;
|
|
|
|
// Create an FTP request to hold command details
|
|
|
|
FTPRequest ftpReq = new FTPRequest();
|
|
|
|
// The server session loops until the NetBIOS hangup state is set.
|
|
|
|
int rdlen = -1;
|
|
String cmd = null;
|
|
|
|
while (m_sock != null)
|
|
{
|
|
|
|
// Wait for a data packet
|
|
|
|
rdlen = m_in.read(m_inbuf);
|
|
|
|
// Check if there is no more data, the other side has dropped
|
|
// the connection
|
|
|
|
if (rdlen == -1)
|
|
{
|
|
closeSession();
|
|
continue;
|
|
}
|
|
|
|
// Trim the trailing <CR><LF>
|
|
|
|
if (rdlen > 0)
|
|
{
|
|
while (rdlen > 0 && m_inbuf[rdlen - 1] == '\r' || m_inbuf[rdlen - 1] == '\n')
|
|
rdlen--;
|
|
}
|
|
|
|
// Get the command string
|
|
|
|
cmd = new String(m_inbuf, 0, rdlen);
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_TIMING))
|
|
startTime = System.currentTimeMillis();
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_PKTTYPE))
|
|
logger.debug("Cmd " + ftpReq);
|
|
|
|
// Parse the received command, and validate
|
|
|
|
ftpReq.setCommandLine(cmd);
|
|
m_reqCount++;
|
|
|
|
switch (ftpReq.isCommand())
|
|
{
|
|
// User command
|
|
|
|
case FTPCommand.User:
|
|
procUser(ftpReq);
|
|
break;
|
|
|
|
// Password command
|
|
|
|
case FTPCommand.Pass:
|
|
procPassword(ftpReq);
|
|
break;
|
|
|
|
// Quit command
|
|
|
|
case FTPCommand.Quit:
|
|
procQuit(ftpReq);
|
|
break;
|
|
|
|
// Type command
|
|
|
|
case FTPCommand.Type:
|
|
procType(ftpReq);
|
|
break;
|
|
|
|
// Port command
|
|
|
|
case FTPCommand.Port:
|
|
procPort(ftpReq);
|
|
break;
|
|
|
|
// Passive command
|
|
|
|
case FTPCommand.Pasv:
|
|
procPassive(ftpReq);
|
|
break;
|
|
|
|
// Restart position command
|
|
|
|
case FTPCommand.Rest:
|
|
procRestart(ftpReq);
|
|
break;
|
|
|
|
// Return file command
|
|
|
|
case FTPCommand.Retr:
|
|
procReturnFile(ftpReq);
|
|
|
|
// Reset the restart position
|
|
|
|
m_restartPos = 0;
|
|
break;
|
|
|
|
// Store file command
|
|
|
|
case FTPCommand.Stor:
|
|
procStoreFile(ftpReq);
|
|
break;
|
|
|
|
// Print working directory command
|
|
|
|
case FTPCommand.Pwd:
|
|
case FTPCommand.XPwd:
|
|
procPrintWorkDir(ftpReq);
|
|
break;
|
|
|
|
// Change working directory command
|
|
|
|
case FTPCommand.Cwd:
|
|
case FTPCommand.XCwd:
|
|
procChangeWorkDir(ftpReq);
|
|
break;
|
|
|
|
// Change to previous directory command
|
|
|
|
case FTPCommand.Cdup:
|
|
case FTPCommand.XCup:
|
|
procCdup(ftpReq);
|
|
break;
|
|
|
|
// Full directory listing command
|
|
|
|
case FTPCommand.List:
|
|
procList(ftpReq);
|
|
break;
|
|
|
|
// Short directory listing command
|
|
|
|
case FTPCommand.Nlst:
|
|
procNList(ftpReq);
|
|
break;
|
|
|
|
// Delete file command
|
|
|
|
case FTPCommand.Dele:
|
|
procDeleteFile(ftpReq);
|
|
break;
|
|
|
|
// Rename file from command
|
|
|
|
case FTPCommand.Rnfr:
|
|
procRenameFrom(ftpReq);
|
|
break;
|
|
|
|
// Rename file to comand
|
|
|
|
case FTPCommand.Rnto:
|
|
procRenameTo(ftpReq);
|
|
break;
|
|
|
|
// Create new directory command
|
|
|
|
case FTPCommand.Mkd:
|
|
case FTPCommand.XMkd:
|
|
procCreateDirectory(ftpReq);
|
|
break;
|
|
|
|
// Delete directory command
|
|
|
|
case FTPCommand.Rmd:
|
|
case FTPCommand.XRmd:
|
|
procRemoveDirectory(ftpReq);
|
|
break;
|
|
|
|
// Return file size command
|
|
|
|
case FTPCommand.Size:
|
|
procFileSize(ftpReq);
|
|
break;
|
|
|
|
// Set modify date/time command
|
|
|
|
case FTPCommand.Mdtm:
|
|
procModifyDateTime(ftpReq);
|
|
break;
|
|
|
|
// System status command
|
|
|
|
case FTPCommand.Syst:
|
|
procSystemStatus(ftpReq);
|
|
break;
|
|
|
|
// Server status command
|
|
|
|
case FTPCommand.Stat:
|
|
procServerStatus(ftpReq);
|
|
break;
|
|
|
|
// Help command
|
|
|
|
case FTPCommand.Help:
|
|
procHelp(ftpReq);
|
|
break;
|
|
|
|
// No-op command
|
|
|
|
case FTPCommand.Noop:
|
|
procNoop(ftpReq);
|
|
break;
|
|
|
|
// Structure command (obsolete)
|
|
|
|
case FTPCommand.Stru:
|
|
procStructure(ftpReq);
|
|
break;
|
|
|
|
// Mode command (obsolete)
|
|
|
|
case FTPCommand.Mode:
|
|
procMode(ftpReq);
|
|
break;
|
|
|
|
// Allocate command (obsolete)
|
|
|
|
case FTPCommand.Allo:
|
|
procAllocate(ftpReq);
|
|
break;
|
|
|
|
// Abort an active file data transfer
|
|
|
|
case FTPCommand.Abor:
|
|
procAbort(ftpReq);
|
|
break;
|
|
|
|
// Return the list of features that this server supports
|
|
|
|
case FTPCommand.Feat:
|
|
procFeatures(ftpReq);
|
|
break;
|
|
|
|
// Unknown/unimplemented command
|
|
|
|
default:
|
|
if (ftpReq.isCommand() != FTPCommand.InvalidCmd)
|
|
sendFTPResponse(502, "Command "
|
|
+ FTPCommand.getCommandName(ftpReq.isCommand()) + " not implemented");
|
|
else
|
|
sendFTPResponse(502, "Command not implemented");
|
|
break;
|
|
}
|
|
|
|
// Debug
|
|
|
|
if (logger.isDebugEnabled() && hasDebug(DBG_TIMING))
|
|
{
|
|
endTime = System.currentTimeMillis();
|
|
long duration = endTime - startTime;
|
|
if (duration > 20)
|
|
logger.debug("Processed cmd "
|
|
+ FTPCommand.getCommandName(ftpReq.isCommand()) + " in " + duration + "ms");
|
|
}
|
|
|
|
// Commit, or rollback, any active user transaction
|
|
|
|
try
|
|
{
|
|
// Commit or rollback the transaction
|
|
|
|
endTransaction();
|
|
}
|
|
catch ( Exception ex)
|
|
{
|
|
// Debug
|
|
|
|
if ( logger.isDebugEnabled())
|
|
logger.debug("Error committing transaction", ex);
|
|
}
|
|
|
|
} // end while state
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
|
|
// DEBUG
|
|
|
|
if (logger.isErrorEnabled() && hasDebug(DBG_STATE))
|
|
logger.error("Socket closed by remote client");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
// Output the exception details
|
|
|
|
if (isShutdown() == false)
|
|
{
|
|
logger.debug(ex);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// If there is an active transaction then roll it back
|
|
|
|
if ( hasUserTransaction())
|
|
{
|
|
try
|
|
{
|
|
getUserTransaction().rollback();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.warn("Failed to rollback transaction", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cleanup the session, make sure all resources are released
|
|
|
|
closeSession();
|
|
|
|
// Debug
|
|
|
|
if (hasDebug(DBG_STATE))
|
|
logger.debug("Server session closed");
|
|
}
|
|
|
|
/**
|
|
* Authenticate an associated FTP data session using the same credentials as the main FTP session
|
|
*
|
|
* @exception AuthenticationException
|
|
*/
|
|
protected void authenticateDataSession() throws org.alfresco.repo.security.authentication.AuthenticationException
|
|
{
|
|
// Use the normal authentication service as we have the plaintext password
|
|
|
|
AuthenticationService authService = getServer().getConfiguration().getAuthenticationService();
|
|
|
|
// Authenticate the user
|
|
|
|
ClientInfo cInfo = getClientInformation();
|
|
|
|
if ( cInfo.isGuest())
|
|
{
|
|
// Authenticate as the guest user
|
|
|
|
authService.authenticateAsGuest();
|
|
}
|
|
else
|
|
{
|
|
// Authenticate as a normal user
|
|
|
|
authService.authenticate( cInfo.getUserName(), cInfo.getPasswordAsCharArray());
|
|
}
|
|
}
|
|
} |