Britt Park b2f9df29d1 Humongous merge. It is incomplete, however; faces-config-navigation.xml and ClientConfigElement
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
2006-11-08 05:17:40 +00:00

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());
}
}
}