Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

A Complete FTP Server

, 30 May 2005
This article presents a fully functional implementation of a FTP server.
/********************************************************************/
/*																	*/
/*  ControlSocket.cpp												*/
/*																	*/
/*  Implementation of the CControlSocket class.						*/
/*	This class is a part of the CConnectThread which handles		*/
/*  socket connections. Incomming data is processed in OnReceive	*/
/*																	*/
/*  Programmed by Pablo van der Meer								*/
/*																	*/
/*  Copyright Pablo Software Solutions 2005							*/
/*	http://www.pablosoftwaresolutions.com							*/
/*																	*/
/*  Last updated: May 28, 2005										*/
/*																	*/
/********************************************************************/

#include "stdafx.h"
#include "FTPServerApp.h"
#include "FTPServer.h"
#include "ControlSocket.h"
#include "ConnectThread.h"
#include "ApplicationDlg.h"
#include "DataSocket.h"

extern CFTPServer theServer;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/********************************************************************/
/*																	*/
/* Function name : CControlSocket::CControlSocket					*/
/* Description   : Constructor										*/
/*																	*/
/********************************************************************/
CControlSocket::CControlSocket()
{
	m_nStatus = STATUS_LOGIN;
	m_pDataSocket = NULL;
	m_strRemoteHost = "";
	m_nRemotePort = -1;
	m_bPassiveMode = FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : CControlSocket::~CControlSocket					*/
/* Description   : Destructor										*/
/*																	*/
/********************************************************************/
CControlSocket::~CControlSocket()
{
	DestroyDataConnection();

	// tell our thread we have been closed
	AfxGetThread()->PostThreadMessage(WM_QUIT,0,0);
}


// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CControlSocket, CSocket)
	//{{AFX_MSG_MAP(CControlSocket)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif	// 0


/********************************************************************/
/*																	*/
/* Function name : OnClose											*/		
/* Description   : Send WM_QUIT message to the thread containing	*/
/*				   the socket to shutdown once the connection is	*/
/*                 closed.											*/
/*																	*/
/********************************************************************/
void CControlSocket::OnClose(int nErrorCode) 
{
	Close();
	// destroy connection
	m_pThread->PostThreadMessage(WM_QUIT, 0, 0);

	CSocket::OnClose(nErrorCode);
}


#define BUFFERSIZE 4096

/********************************************************************/
/*																	*/
/* Function name : OnReceive										*/		
/* Description   : Called by the framework to notify this socket	*/
/*                 that there is data in the buffer.				*/
/*																	*/
/********************************************************************/
void CControlSocket::OnReceive(int nErrorCode) 
{
	TCHAR buff[BUFFERSIZE+1];

	int nRead = Receive(buff, BUFFERSIZE);
	switch (nRead)
	{
		case 0:
			Close();
			break;

		case SOCKET_ERROR:
			if (GetLastError() != WSAEWOULDBLOCK) 
			{
				TCHAR szError[256];
				wsprintf(szError, "OnReceive error: %d", GetLastError());
				
				AfxMessageBox (szError);
			}
			break;

		default:
			if (nRead != SOCKET_ERROR && nRead != 0)
			{
				((CConnectThread *)AfxGetThread())->IncReceivedBytes(nRead);

				// terminate the string
				buff[nRead] = 0; 
				m_RxBuffer += CString(buff);
				
				GetCommandLine();
			}	
			break;
	}
	CSocket::OnReceive(nErrorCode);
}


/********************************************************************/
/*																	*/
/* Function name: GetCommandLine									*/		
/* Description  : Parse complete command line						*/
/*																	*/
/********************************************************************/
void CControlSocket::GetCommandLine()
{
	CString strTemp;
	int nIndex;

	while(!m_RxBuffer.IsEmpty())
	{
		nIndex = m_RxBuffer.Find("\r\n");
		if (nIndex != -1)
		{
			strTemp = m_RxBuffer.Left(nIndex);
			m_RxBuffer = m_RxBuffer.Mid(nIndex + 2);
			if (!strTemp.IsEmpty())
			{
				m_strCommands.AddTail(strTemp);
				// parse and execute command
				ProcessCommand();
			}
		}
		else
			break;
	}
}


/********************************************************************/
/*																	*/
/* Function name: GetCommand										*/		
/* Description  : Get command from receiver buffer.					*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetCommand(CString &strCommand, CString &strArguments)
{
	if (!m_strCommands.IsEmpty())
	{
		CString strBuff = m_strCommands.RemoveHead();

		// log command line
		PostStatusMessage(strBuff, 1);

		int nIndex = strBuff.Find(" ");
		if (nIndex != -1)
		{
			strCommand = strBuff.Left(nIndex);
			strArguments = strBuff.Mid(nIndex+1);
		}
		else
		{
			strCommand = strBuff;
		}

		if (!strCommand.IsEmpty())
		{
			strCommand.MakeUpper();

			// who screwed up ???
			if (strCommand.Right(4) == "ABOR")
			{
				strCommand = "ABOR";
			}
			return TRUE;
		}
	}
	return FALSE;
}


/********************************************************************/
/*																	*/
/* Function name: SendResponse										*/		
/* Description  : Send response to client.							*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::SendResponse(LPCTSTR pstrFormat, ...)
{
	CString str;

	// format arguments and put them in CString
	va_list args;
	va_start(args, pstrFormat);
	str.FormatV(pstrFormat, args);

	int nBytes = CSocket::Send(str + "\r\n", str.GetLength()+2);
	if (nBytes == SOCKET_ERROR)
	{
		Close();
		PostStatusMessage("Could not send reply, disconnected.", 2);	

		// tell our thread we have been closed
		m_pThread->PostThreadMessage(WM_QUIT, 1, 0);

		return FALSE;
	}

	PostStatusMessage(str, 2);

	((CConnectThread *)AfxGetThread())->IncSentBytes(nBytes);
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name: ProcessCommand									*/
/* Description  : Parse and execute command from client.			*/
/*																	*/
/********************************************************************/
void CControlSocket::ProcessCommand()
{
	CString strCommand, strArguments;

	// get command
	CString strBuff = m_strCommands.RemoveHead();
	int nIndex = strBuff.Find(" ");
	if (nIndex == -1)
	{
		strCommand = strBuff;
	}
	else
	{
		strCommand = strBuff.Left(nIndex);
		strArguments = strBuff.Mid(nIndex+1);
	}
	strCommand.MakeUpper();
	
	// log command line
	PostStatusMessage(strCommand + " " + strArguments, 1);

	if ((m_nStatus == STATUS_LOGIN) && (strCommand != "USER" && strCommand != "PASS"))
	{
		SendResponse("530 Please login with USER and PASS.");
		return;
	}
	
	// username entered
	if (strCommand == "USER")
	{
		m_nStatus = STATUS_LOGIN;
		m_strUserName = strArguments;

		CString strPeerAddress;
		UINT nPeerPort;
		GetPeerName(strPeerAddress, nPeerPort);

		// tell FTP server a new user has connected
		CConnectThread *pThread = (CConnectThread *)m_pThread;
		((CFTPServer *)pThread->m_pWndServer)->m_pEventSink->OnFTPUserConnected(m_pThread->m_nThreadID, m_strUserName, strPeerAddress);

		SendResponse("331 Password required for %s", m_strUserName);
	}
	else
	// password entered
	if (strCommand == "PASS")
	{
		// already logged on ?
		if (m_strUserName.IsEmpty())
		{
			SendResponse("503 Login with USER first.");
		}
		else
		{
			// now we have user name and password, attempt to login the client
			if (theServer.m_UserManager.GetUser(m_strUserName, m_User))
			{
				// check password
				if ((!m_User.m_strPassword.Compare(strArguments) || m_User.m_strPassword.IsEmpty()) && !m_User.m_bAccountDisabled)
				{
					// set home directory of user
					m_strCurrentDir = "/";

					// succesfully logged on
					m_nStatus = STATUS_IDLE;
					SendResponse("230 User successfully logged in.");
					return;
				}
			}
			SendResponse("530 Not logged in, user or password incorrect!");
		}
	}		
	else
	// close connection
	if ((strCommand == "QUIT") || (strCommand == "BYE"))
	{
		// send goodbye message to client
		CConnectThread *pThread = (CConnectThread *)m_pThread;
		SendResponse("220 %s", ((CFTPServer *)pThread->m_pWndServer)->GetGoodbyeMessage());

		Close();
		
		// tell our thread we have been closed
	
		// destroy connection
		m_pThread->PostThreadMessage(WM_QUIT, 1, 0);
	}
	else
	// change transfer type
	if (strCommand == "TYPE")
	{
		// let's pretend we did something...
		SendResponse("200 Type set to %s", strArguments);
	}
	else
	// print current directory
	if (strCommand == "PWD")
	{
		SendResponse("257 \"%s\" is current directory.", m_strCurrentDir);
	}
	else
	// change to parent directory
	if (strCommand == "CDUP")
	{
		DoChangeDirectory("..");
	}
	else
	// change working directory
	if (strCommand == "CWD")
	{
		DoChangeDirectory(strArguments);
	}
	else
	// specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2. 
	if (strCommand == "PORT")
	{
		CString strSub;
		int nCount=0;

		while (AfxExtractSubString(strSub, strArguments, nCount++, ','))
		{
			switch(nCount)
			{
				case 1:	// a1
					m_strRemoteHost = strSub;
					m_strRemoteHost += ".";
					break;
				case 2:	// a2
					m_strRemoteHost += strSub;
					m_strRemoteHost += ".";
					break;
				case 3:	// a3
					m_strRemoteHost += strSub;
					m_strRemoteHost += ".";
					break;
				case 4:	// a4
					m_strRemoteHost += strSub;
					break;
				case 5:	// p1
					m_nRemotePort = 256*atoi(strSub);
					break;
				case 6:	// p2
					m_nRemotePort += atoi(strSub);
					break;
			}
		}
		SendResponse("200 Port command successful.");
	}
	else
	// switch to passive mode
	if (strCommand == "PASV")
	{	
		// delete existing datasocket
		DestroyDataConnection();

		// create new data socket
		m_pDataSocket = new CDataSocket(this);

		if (!m_pDataSocket->Create())
		{
			DestroyDataConnection();	
			SendResponse("421 Failed to create socket.");
			return;
		}
		// start listening
		m_pDataSocket->Listen();
		m_pDataSocket->AsyncSelect();
		
		CString strIPAddress, strTmp;
		UINT nPort;
		
		// get our ip address
		GetSockName(strIPAddress, nPort);
		// get listen port
		m_pDataSocket->GetSockName(strTmp, nPort);
		// replace dots 
		strIPAddress.Replace(".", ",");
		// tell the client which address/port to connect to
		SendResponse("227 Entering Passive Mode (%s,%d,%d).", strIPAddress, nPort/256, nPort%256);
		
		m_bPassiveMode = TRUE;
	} 
	else
	// list current directory (or a specified file/directory)
	if (strCommand == "LIST")
	{
		CString strListing;
		if (!GetDirectoryList(strArguments, strListing))
		{
			// something went wrong
			return;
		}

		SendResponse("150 Opening ASCII mode data connection for directory list."); 

		// create data connection with client
		if (CreateDataConnection())
		{
			if (strListing.IsEmpty())
			{
				// close data connection with client
				DestroyDataConnection();

				SendResponse("226 Transfer complete."); 
				m_nStatus = STATUS_IDLE;
				return;
			}
		}
		else
		{
			// close data connection with client
			DestroyDataConnection();
			return;
		}

		m_nStatus = STATUS_LIST;
		
		// send the listing
		m_pDataSocket->SendListing(strListing);
	} 
	else
	// retrieve file
	if (strCommand == "RETR")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DOWNLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
				SendResponse("150 Opening BINARY mode data connection for file transfer.");

				// create socket connection for file transfer
				if (!CreateDataConnection())
				{
					m_nStatus = STATUS_IDLE;
					// close data connection with client
					DestroyDataConnection();
				}
				else
				{
					m_nStatus = STATUS_DOWNLOAD;
					// send the file
					m_pDataSocket->SendFile(strResult);
				}
				break;
		}
	}
	else
	// client wants to upload file
	if (strCommand == "STOR")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_UPLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 Filename invalid.");
				break;
			default:
				SendResponse("150 Opening BINARY mode data connection for file transfer.");

				// create socket connection for file transfer
				if (!CreateDataConnection())
				{
					m_nStatus = STATUS_IDLE;
					// close data connection with client
					DestroyDataConnection();
				}
				else
				{
					m_nStatus = STATUS_UPLOAD;
					// retrieve the file
					m_pDataSocket->RetrieveFile(strResult);
				}
				break;
		}
	}
	else
	// get file size
	if (strCommand == "SIZE")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DOWNLOAD, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
			{
				CFileStatus status;
				CFile::GetStatus(strResult, status);
				SendResponse("213 %d", status.m_size);
				break;
			}
		}
	}
	else
	// delete file
	if (strCommand == "DELE")
	{
		CString strResult;
		int nResult = CheckFileName(strArguments, FTP_DELETE, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_FILE_NOT_FOUND:
				SendResponse("550 File not found.");
				break;
			default:
				CString strRelativePath;
				GetRelativePath(strResult, strRelativePath);

				// delete the file
				if (!DeleteFile(strResult))
				{
					SendResponse("450 Internal error deleting the file: \"%s\".",  strRelativePath);
				}
				else
				{
					SendResponse("250 File \"%s\" was deleted successfully.", strRelativePath);
				}
				break;
		}
	}
	else
	// remove directory
	if (strCommand == "RMD")
	{		
		CString strResult;
		int nResult = CheckDirectory(strArguments, FTP_DELETE, strResult);
		switch(nResult)
		{
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Permission denied.");
				break;
			case ERROR_PATH_NOT_FOUND:
				SendResponse("550 Directory not found.");
				break;
			default:
				// remove the directory
				if (!RemoveDirectory(strResult))
				{
					if (GetLastError() == ERROR_DIR_NOT_EMPTY)
					{
						SendResponse("550 Directory not empty.");
					}
					else
					{
						SendResponse("450 Internal error deleting the directory.");
					}
				}
				else
				{
					SendResponse("250 Directory deleted successfully.");
				}
				break;
		}
	}
	else
	// create directory
	if (strCommand == "MKD")
	{		
		CString strResult;
		int nResult = CheckDirectory(strArguments, FTP_CREATE_DIR, strResult);
		switch(nResult)
		{
			case ERROR_SUCCESS:
				SendResponse("550 Directory already exists.");
				break;
			case ERROR_ACCESS_DENIED:
				SendResponse("550 Can't create directory. Permission denied.");
				break;
			default:
				// create directory structure
				if (!MakeSureDirectoryPathExists(strResult))
				{
					SendResponse("450 Internal error creating the directory.");
				}
				else
				{
					SendResponse("250 Directory created successfully.");
				}
				break;
		}
	}
	else
	// abort transfer
	if (strCommand == "ABOR")
	{		
		if (m_pDataSocket)
		{
			if (m_nStatus != STATUS_IDLE)
			{
				SendResponse("426 Data connection closed.");
			}
			// destroy data connection
			m_pThread->PostThreadMessage(WM_DESTROYDATACONNECTION, 0, 0);
		}
		SendResponse("226 ABOR command successful.");
	} 
	else
	// get system info
	if (strCommand == "SYST")
	{
		SendResponse("215 UNIX emulated by Quick 'n Easy FTP Server.");
	}
	else
	// dummy instruction
	if (strCommand == "NOOP")
	{
		SendResponse("200 OK");
	}
	else
	{
		SendResponse("502 Command not implemented.");
	}
}


/********************************************************************/
/*																	*/
/* Function name: PostStatusMessage									*/		
/* Description  : Post status message.								*/
/*																	*/
/********************************************************************/
void CControlSocket::PostStatusMessage(LPCTSTR lpszStatus, int nType)
{
	CConnectThread *pThread = (CConnectThread *)m_pThread;
	((CFTPServer *)pThread->m_pWndServer)->AddTraceLine(nType, "[%u] %s", m_pThread->m_nThreadID, lpszStatus);
}


/********************************************************************/
/*																	*/
/* Function name: CreateDataConnection								*/		
/* Description  : Create data transfer connection.					*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::CreateDataConnection()
{
	if (!m_bPassiveMode)
	{
		// need PORT command first !
		if (m_strRemoteHost == "" || m_nRemotePort == -1)
		{
			SendResponse("425 Can't open data connection.");
			return FALSE;
		}

		m_pDataSocket = new CDataSocket(this);
		if (m_pDataSocket->Create())
		{
			m_pDataSocket->AsyncSelect();

			// connect to remote site
			if (m_pDataSocket->Connect(m_strRemoteHost, m_nRemotePort) == 0)
			{
				if (GetLastError() != WSAEWOULDBLOCK)
				{
					SendResponse("425 Can't open data connection.");
					return FALSE;
				}
			}
		}
		else
		{
			SendResponse("421 Failed to create data connection socket.");
			return FALSE;
		}
	}

	// wait until we're connected (for max 10 seconds)
	DWORD dwTickCount = GetTickCount() + 10000;
	while (!m_pDataSocket->m_bConnected)
	{
		DoEvents();
		if (dwTickCount < GetTickCount())
		{
			SendResponse("421 Failed to create data connection socket.");
			return FALSE;
		}
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name: DestroyDataConnection								*/		
/* Description  : Close data transfer connection.					*/
/*																	*/
/********************************************************************/
void CControlSocket::DestroyDataConnection()
{
	if (!m_pDataSocket)
		return;

	delete m_pDataSocket;

	// reset transfer status
	m_pDataSocket = NULL;
	m_strRemoteHost = "";
	m_nRemotePort = -1;
	m_bPassiveMode = FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : GetLocalPath										*/
/* Description   : Convert relative path to local path				*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetLocalPath(LPCTSTR lpszRelativePath, CString &strLocalPath)
{
	CString strRelativePath = lpszRelativePath;

	// make unix style
	strRelativePath.Replace("\\","/");
	while(strRelativePath.Replace("//","/"));

	CStringList partList;
	CString strSub;
	int nCount=0;
	BOOL bPathExists = TRUE;

	// split path in parts
	while(AfxExtractSubString(strSub, strRelativePath, nCount++, '/'))
	{
		// get rid of insignificant dots
		if (strSub != "..")
		{
			strSub.TrimLeft('.');
			strSub.TrimRight('.');
		}

		if (!strSub.IsEmpty())
			partList.AddTail(strSub);
	}
	
	// search for home directory
	CString strHomeDir;
	if (lpszRelativePath[0] == '/')
	{
		// absolute path
		strHomeDir = m_User.m_strHomeDirectory;
	}
	else
	{
		// relative path
		GetLocalPath(m_strCurrentDir, strHomeDir);
	}

	while(!partList.IsEmpty())
	{
		CString strPart = partList.GetHead();
		partList.RemoveHead();

		CString strCheckDir;
		
		if (strPart == "..")
		{
			// go back one level
			int nPos = strHomeDir.ReverseFind('\\');
			if (nPos != -1)
			{
				strCheckDir = strHomeDir.Left(nPos);
			}
		}
		else
		{
			strCheckDir = strHomeDir + "\\" + strPart;
		}
	
		// does directory exist ?
		if (FileExists(strCheckDir, TRUE))
		{
			strHomeDir = strCheckDir;
		}
		else
		// does file exist ?
		if (FileExists(strCheckDir, FALSE))
		{
			strHomeDir = strCheckDir;
		}
		else
		{
			strHomeDir = strCheckDir;

			// directory not found
			bPathExists = FALSE;
		}
	}
	
	// successfully converted directory
	strLocalPath = strHomeDir;

	// fix for root drive
	if (strLocalPath.Right(1) == ':')
		strLocalPath += "\\";

	return bPathExists;
}


/********************************************************************/
/*																	*/
/* Function name : GetRelativePath									*/		
/* Description   : Convert local path to relative path 				*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetRelativePath(LPCTSTR lpszLocalPath, CString &strRelativePath)
{
	CString strOffset = m_User.m_strHomeDirectory;
	CString strLocalPath = lpszLocalPath;

	strOffset.MakeLower();
	strLocalPath.MakeLower();

	if (strOffset.Right(1) != '\\')
		strOffset += "\\";

	if (strLocalPath.Right(1) != '\\')
		strLocalPath += "\\";

	if (strOffset == strLocalPath)
	{
		strRelativePath = "/";
	}
	else
	{
		strRelativePath = strLocalPath;
		strRelativePath.Replace(strOffset, "/");
		strRelativePath.Replace("\\", "/");
		strRelativePath.TrimRight('/');
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name : CheckAccessRights								*/
/* Description   : Check if user has access to specified directory.	*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::CheckAccessRights(LPCTSTR lpszDirectory, int nOption)
{
	// start with full path
	CString strCheckDir = lpszDirectory;

	while(!strCheckDir.IsEmpty())
	{
		CString strPath1 = strCheckDir;
		strPath1.TrimRight("\\");
		CString strPath2 = m_User.m_strHomeDirectory;
		strPath2.TrimRight("\\");

		// found a match ?
		if (strPath1.CompareNoCase(strPath2) == 0)
		{
			// check file access rights
			if (((!m_User.m_bAllowDownload) && (nOption == FTP_DOWNLOAD)) ||
				((!m_User.m_bAllowUpload) && (nOption == FTP_UPLOAD)) ||
				((!m_User.m_bAllowRename) && (nOption == FTP_RENAME)) ||
				((!m_User.m_bAllowDelete) && (nOption == FTP_DELETE)) ||
				((!m_User.m_bAllowCreateDirectory) && (nOption == FTP_CREATE_DIR)) ||
				((!m_User.m_bAllowDownload && !m_User.m_bAllowUpload) && (nOption == FTP_LIST)))
			{
				return FALSE;
			}
			return TRUE;
		}
		int nPos = strCheckDir.ReverseFind('\\');
		if (nPos != -1)
		{
			// strip subdir 
			strCheckDir = strCheckDir.Left(nPos);
		}
		else
		{
			// we're done
			strCheckDir.Empty();
		}
	} 
	// users has no rights to this directory
	return FALSE;
}


/********************************************************************/
/*																	*/
/* Function name : GetDirectoryList									*/
/* Description   : Get directory listing for specfied directory		*/
/*																	*/
/********************************************************************/
BOOL CControlSocket::GetDirectoryList(LPCTSTR lpszDirectory, CString &strResult)
{
	CString strDirectory = lpszDirectory;

	// clear listing
	strResult = "";

	// just return current directory
	strDirectory.TrimRight('*');

	// if client did not specify a directory -> use current dir
	if (strDirectory.IsEmpty())
	{
		strDirectory = m_strCurrentDir;
	}

	CString strLocalPath;
	int nResult = CheckDirectory(strDirectory, FTP_LIST, strLocalPath);
	switch(nResult)
	{
		case ERROR_ACCESS_DENIED:
			// user has no permissions
			SendResponse("550 \"%s\": Permission denied.", lpszDirectory);
			return FALSE;

		case ERROR_PATH_NOT_FOUND:
			// unable to convert to local path
			SendResponse("550 \"%s\": Directory not found.", lpszDirectory);
			return FALSE;

		default:
			// everything is successful
			break;
	}

	CFileFind find;
	BOOL bFound = FALSE;

	// check if it's a directory
	if ((GetFileAttributes(strLocalPath) & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
	{
		CString strPath = strLocalPath;
		if (strPath.Right(1)==_T("\\"))
			bFound = find.FindFile(strLocalPath + "*.*");
		else
			bFound = find.FindFile(strLocalPath + "\\*.*");
	}
	else
	{
		// it's a file
		bFound = find.FindFile(strLocalPath);
	}

	while (bFound)
	{
		bFound = find.FindNextFile();
		
		// skip "." and ".." 
		if (find.IsDots())
			continue;

		// permissions
		if (find.IsDirectory())
			strResult += "drwx------";
		else
			strResult += "-rwx------";

		// groups
		strResult += " 1 user group ";
		// file size
		CString strLength;
		strLength.Format("%d", find.GetLength());
		CString strFiller = "              ";
		strResult += strFiller.Left(strFiller.GetLength() - strLength.GetLength());
		strResult += strLength;
		// file date
		strResult += GetFileDate(find);
		// file name
		strResult += find.GetFileName();
		// end of line
		strResult += "\r\n";
	}
	return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name : CheckFileName									*/
/* Description   : Check if filename is valid and if user has		*/
/*				   access rights.									*/
/*																	*/
/********************************************************************/
int CControlSocket::CheckFileName(CString strFilename, int nOption, CString &strResult)
{
	// make unix style
	strFilename.Replace("\\", "/");
	while(strFilename.Replace("//", "/"));
	strFilename.TrimRight("/");
	
	if (strFilename == "")
	{
		// no file name
		return ERROR_FILE_NOT_FOUND;
	}

	// append filename to directory
	CString strDirectory = m_strCurrentDir;

	// client has specified complete path 
	int nPos = strFilename.ReverseFind('/');
	if (nPos != -1)
	{
		strDirectory = strFilename.Left(nPos);
		if (strDirectory == "")
			strDirectory = "/";
		strFilename = strFilename.Mid(nPos+1);
	}

	// get local path
	CString strLocalPath;
	if (!GetLocalPath(strDirectory, strLocalPath))
	{
		// directory does not exist
		return ERROR_FILE_NOT_FOUND;
	}

	// create the complete path
	strResult = strLocalPath + "\\" + strFilename;

	if ((nOption != FTP_UPLOAD) && !FileExists(strResult, FALSE))
	{
		// file does not exist
		return ERROR_FILE_NOT_FOUND;
	}

	// check file access rights
	if (!CheckAccessRights(strLocalPath, nOption))
	{
		// user has no permissions, to execute specified action
		return ERROR_ACCESS_DENIED;
	}
	
	// everything is ok
	return ERROR_SUCCESS;
}


/********************************************************************/
/*																	*/
/* Function name : CheckDirectory									*/
/* Description   : Check if directory exists and if user has		*/
/*				   appropriate permissions.							*/
/*																	*/
/********************************************************************/
int CControlSocket::CheckDirectory(CString strDirectory, int nOption, CString &strResult)
{
	// make unix compatible
	strDirectory.Replace("\\","/");
	while(strDirectory.Replace("//","/"));
	strDirectory.TrimRight("/");
	
	if (strDirectory.IsEmpty())
	{
		if (nOption == FTP_LIST)
		{
			strDirectory = "/";
		}
		else
		{
			// no directory
			return ERROR_PATH_NOT_FOUND;
		}
	}
	else
	{
		// first character '/' ?
		if (strDirectory.Left(1) != "/")
		{ 
			// client specified a path relative to their current path
			if (m_strCurrentDir.Right(1) != '/')
				strDirectory = m_strCurrentDir + "/" + strDirectory;
			else
				strDirectory = m_strCurrentDir + strDirectory;
		}
	}

	// does directory exist ?
	BOOL bPathExists = GetLocalPath(strDirectory, strResult);
		
	if (!bPathExists && (nOption != FTP_CREATE_DIR))
	{ 
		return ERROR_PATH_NOT_FOUND;
	}

	// check directory access rights
	if (!CheckAccessRights(strResult, nOption))
	{
		// user has no permissions, to execute specified action
		return ERROR_ACCESS_DENIED;
	}

	// did directory exist ?
	if (!bPathExists)
		return ERROR_PATH_NOT_FOUND;

	// function successfull
	return ERROR_SUCCESS;
}


/********************************************************************/
/*																	*/
/* Function name : DoChangeDirectory								*/
/* Description   : Change to specified directory.					*/
/*																	*/
/********************************************************************/
void CControlSocket::DoChangeDirectory(LPCTSTR lpszDirectory)
{
	// try to change to specified directory
	CString strLocalPath;
	int nResult = CheckDirectory(lpszDirectory, FTP_LIST, strLocalPath);
	switch(nResult)
	{
		case ERROR_ACCESS_DENIED:
			// user has no permissions
			SendResponse("550 \"%s\": Permission denied.", lpszDirectory);
			break;
		case ERROR_PATH_NOT_FOUND:
			// unable to convert to local path
			SendResponse("550 \"%s\": Directory not found.", lpszDirectory);
			break;
		default:
			// everything is successful
			CString strRelativePath;
			if (GetRelativePath(strLocalPath, strRelativePath))
			{
				m_strCurrentDir = strRelativePath;
			}
			SendResponse("250 \"%s\" is current directory.", m_strCurrentDir);
			break;
	}
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Pablo van der Meer
Web Developer
Netherlands Netherlands
No Biography provided

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 30 May 2005
Article Copyright 2002 by Pablo van der Meer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid