www.pudn.com > BesidesFTPServer.rar > ControlSocket.cpp


/********************************************************************/ 
/*																	*/ 
/*  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								*/ 
/*  Based partially on and inspired by FileZilla Server.			*/ 
/*																	*/ 
/*	http://www.pablovandermeer.nl									*/ 
/*																	*/ 
/*  Last updated: October 20, 2002									*/ 
/*																	*/ 
/********************************************************************/ 
 
#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_bRenameFile = FALSE; 
	m_pDataSocket = NULL; 
	m_strRemoteHost = ""; 
	m_nRemotePort = -1; 
	m_dwRestartOffset = 0; 
	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_THREADMSG, 1, 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]; 
 
	int nRead = Receive(buff, BUFFERSIZE); 
	switch (nRead) 
	{ 
		case 0: 
			Close(); 
			break; 
 
		case SOCKET_ERROR: 
			if (GetLastError() != WSAEWOULDBLOCK)  
			{ 
				TCHAR szError[256]; 
				wsprintf(szError, "OnReceive ´يخَ: %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: HasConnectionDropped								*/		 
/* Description  : Check if connection has been dropped.				*/ 
/*				  Used to detect if client has crashed.				*/ 
/*																	*/ 
/********************************************************************/ 
BOOL CControlSocket::HasConnectionDropped(void) 
{ 
	BOOL bConnDropped = FALSE; 
	INT iRet = 0; 
	BOOL bOK = TRUE; 
	 
	if (m_hSocket == INVALID_SOCKET) 
		return TRUE; 
 
	struct timeval timeout = { 0, 0 }; 
	fd_set readSocketSet; 
	 
	FD_ZERO(&readSocketSet); 
	FD_SET(m_hSocket, &readSocketSet); 
	 
	iRet = ::select(0, &readSocketSet, NULL, NULL, &timeout); 
	bOK = (iRet > 0); 
	 
	if(bOK) 
	{ 
		bOK = FD_ISSET(m_hSocket, &readSocketSet); 
	} 
	 
	if(bOK) 
	{ 
		CHAR szBuffer[1] = ""; 
		iRet = ::recv(m_hSocket, szBuffer, 1, MSG_PEEK); 
		bOK = (iRet > 0); 
		if(!bOK) 
		{ 
			INT iError = ::WSAGetLastError(); 
			bConnDropped = (( iError == WSAENETRESET) || 
				(iError == WSAECONNABORTED) || 
				(iError == WSAECONNRESET) || 
				(iError == WSAEINVAL) || 
				(iRet == 0)); 
		} 
	} 
    return(bConnDropped); 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* 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); 
 
	// is connection still active ? 
	if (HasConnectionDropped()) 
	{ 
		PostStatusMessage("Could not send reply, disconnected.", 2);	 
 
		Close(); 
		// tell our thread we have been closed 
		 
		// destroy connection 
		m_pThread->PostThreadMessage(WM_THREADMSG, 1, 0); 
		return FALSE; 
	} 
 
	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_THREADMSG, 1, 0); 
 
		return FALSE; 
	} 
 
	PostStatusMessage(str, 2); 
 
	((CConnectThread *)AfxGetThread())->IncSentBytes(nBytes); 
	return TRUE; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name: ProcessCommand									*/ 
/* Description  : Parse and execute command from client.			*/ 
/*																	*/ 
/* Based on code provided by FileZilla Server.						*/ 
/* http://sourceforge.net/projects/filezilla						*/ 
/*																	*/ 
/********************************************************************/ 
void CControlSocket::ProcessCommand() 
{ 
	static CFTPCommand commandList[] =  
	{ 
		{TOK_ABOR,	"ABOR", FALSE,	"Abort transfer: ABOR"},  
		{TOK_APPE,	"APPE", TRUE,	"Append file to existing file: APPE file-name"},  
		{TOK_BYE,	"BYE",  FALSE,	"Logout or break the connection: BYE"}, 
		{TOK_CDUP,	"CDUP", FALSE,	"Change to parent directory: CDUP"}, 
		{TOK_CWD,	"CWD",	TRUE,	"Change working directory: CWD [directory-name]"}, 
		{TOK_DELE,	"DELE", TRUE ,	"Delete file: DELE file-name"}, 
		{TOK_HELP,	"HELP",  FALSE, "Show help: HELP [command]"}, 
		{TOK_LIST,	"LIST", FALSE,	"Get directory listing: LIST [path-name]"},  
		{TOK_MKD,	"MKD",	TRUE,	"Make directory: MKD path-name"}, 
		{TOK_NLST,	"NLST",  FALSE,	"List filenames only: NLST [path-name]"}, 
		{TOK_NOOP,	"NOOP", FALSE,	"Do nothing: NOOP"}, 
		{TOK_PASS,	"PASS", TRUE,	"Supply a user password: PASS password"}, 
		{TOK_PASV,	"PASV", FALSE,	"Set server in passive mode: PASV"}, 
		{TOK_PASW,	"P@SW", FALSE,	"Set server in passive mode: P@SW"}, 
		{TOK_PORT,	"PORT", TRUE,	"Specify the client port number: PORT a0,a1,a2,a3,a4,a5"}, 
		{TOK_PWD,	"PWD",	FALSE,	"Get current directory: PWD"}, 
		{TOK_QUIT,	"QUIT",  FALSE,	"Logout or break the connection: QUIT"}, 
		{TOK_REST,	"REST", TRUE,	"Set restart transfer marker: REST marker"}, 
		{TOK_RETR,	"RETR", TRUE,	"Get file: RETR file-name"}, 
		{TOK_RMD,	"RMD",	TRUE,	"Remove directory: RMD path-name"}, 
		{TOK_RNFR,	"RNFR", TRUE,	"Specify old path name of file to be renamed: RNFR file-name"}, 
		{TOK_RNTO,	"RNTO", TRUE,	"Specify new path name of file to be renamed: RNTO file-name"}, 
		{TOK_SIZE,	"SIZE", TRUE,	"Get filesize: SIZE file-name"}, 
		{TOK_STOR,	"STOR", TRUE,	"Store file: STOR file-name"}, 
		{TOK_SYST,	"SYST", FALSE,	"Get operating system type: SYST"}, 
		{TOK_TYPE,	"TYPE", TRUE,	"Set filetype: TYPE [A | I]"}, 
		{TOK_USER,	"USER", TRUE,	"Supply a username: USER username"}, 
		{TOK_XCWD,	"XCWD",	TRUE,	"Change working directory: XCWD [directory-name]"}, 
		{TOK_XMKD,	"XMKD",	TRUE,	"Make directory: XMKD path-name"}, 
		{TOK_XPWD,	"XPWD",	FALSE,	"Get current directory: XPWD"}, 
		{TOK_XRMD,	"XRMD",	TRUE,	"Remove directory: XRMD path-name"}, 
		{TOK_ERROR,	"",		FALSE,  ""}, 
	}; 
	 
	// parse command 
	CString strCommand, strArguments; 
	int nCommand; 
 
	if (!GetCommand(strCommand, strArguments)) 
	{ 
		return; 
	} 
 
	// find command in command list 
	for (nCommand = TOK_ABOR; nCommand < TOK_ERROR; nCommand++) 
	{ 
		// found command ? 
		if (strCommand == commandList[nCommand].m_pszName) 
		{ 
			// did we expect an argument ? 
			if (commandList[nCommand].m_bHasArguments && (strArguments.IsEmpty())) 
			{ 
				SendResponse("501 Syntax error: Invalid number of parameters."); 
				return; 
			} 
			break;			 
		} 
	} 
 
	// no commands are excepted before successful logged on 
	if ((nCommand != TOK_USER && nCommand != TOK_PASS) && (m_nStatus == STATUS_LOGIN)) 
	{ 
		SendResponse("530 Please login with USER and PASS."); 
		return; 
	} 
 
	// proces command 
	switch(nCommand) 
	{ 
		// specify username 
		case TOK_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); 
		} 
		break; 
 
		// specify password 
		case TOK_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."); 
						break; 
					} 
				} 
				SendResponse("530 Not logged in, user or password incorrect!"); 
			} 
		} 
		break; 
		 
		// change transfer type 
		case TOK_TYPE: 
		{ 
			// let's pretend we did something... 
			SendResponse("200 Type set to %s", strArguments); 
		} 
		break; 
 
		// print current directory 
		case TOK_PWD: 
		case TOK_XPWD: 
		{ 
			SendResponse("257 \"%s\" is current directory.", m_strCurrentDir); 
		} 
		break; 
 
		// change to parent directory 
		case TOK_CDUP: 
			strArguments = ".."; 
 
		// change working directory 
		case TOK_CWD: 
		case TOK_XCWD: 
		{ 
			// try to change to specified directory 
			CString strLocalPath; 
			int nResult = CheckDirectory(strArguments, FTP_LIST, strLocalPath); 
			switch(nResult) 
			{ 
				case ERROR_ACCESS_DENIED: 
					// user has no permissions 
					SendResponse("550 \"%s\": Permission denied.", strArguments); 
					break; 
				case ERROR_PATH_NOT_FOUND: 
					// unable to convert to local path 
					SendResponse("550 \"%s\": Directory not found.", strArguments); 
					break; 
				default: 
					// everything is successful 
					CString strRelativePath; 
					if (GetRelativePath(strLocalPath, strRelativePath)) 
					{ 
						m_strCurrentDir = strRelativePath; 
					} 
					SendResponse("250 \"%s\" is current directory.", m_strCurrentDir); 
					break; 
			} 
		} 
		break;  
 
		// specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2.  
		case TOK_PORT: 
		{ 
			CStringArray strArray; 
			// split argument into seperate parts 
			Split(strArguments, ',', strArray); 
			// determine remote hostname 
			m_strRemoteHost.Format("%s.%s.%s.%s", strArray[0], strArray[1], strArray[2], strArray[3]); 
			// determine remote port 
			m_nRemotePort = 256*atoi(strArray[4]) + atoi(strArray[5]); 
			// we're not in PASV mode 
			m_bPassiveMode = FALSE; 
			SendResponse("200 Port command successful."); 
			break; 
		} 
		 
		// switch to passive mode 
		case TOK_PASV: 
		case TOK_PASW: // takes care of SMC Barricade-bug (thanks to: Dmitri Shvetsov) 
		{ 
			// in PASV mode the server create a listing socket for the client to connect to. 
			 
			// delete existing datasocket 
			DestroyDataConnection(); 
 
			// create new data socket 
			m_pDataSocket = new CDataSocket(this); 
 
			if (!m_pDataSocket->Create()) 
			{ 
				DestroyDataConnection();	 
				SendResponse("421 Failed to create socket."); 
				break; 
			} 
			// 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; 
			break; 
		}  
 
		// list current directory (or a specified file/directory) 
		case TOK_LIST: 
		// list filenames only 
		case TOK_NLST: 
		{ 
			CString strListing; 
			if (!GetDirectoryList(strArguments, strListing, (nCommand == TOK_NLST))) 
			{ 
				// 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; 
			 
			m_pDataSocket->AsyncSelect(); 
 
			// send the listing 
			m_pDataSocket->SendListing(strListing); 
			break; 
		}  
		 
		// retrieve file 
		case TOK_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; 
						 
						m_pDataSocket->AsyncSelect(); 
 
						// send the file 
						m_pDataSocket->SendFile(strResult); 
					} 
					break; 
			} 
			break;	 
		} 
 
		// client wants to upload file 
		case TOK_STOR: 
		// client wants to append file 
		case TOK_APPE: 
		{ 
			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: 
					m_dwRestartOffset = 0; 
 
					// determine offset for appending file 
					if (nCommand == TOK_APPE) 
					{ 
						CFileStatus status; 
						if (CFile::GetStatus(strResult, status)) 
						{ 
							m_dwRestartOffset = status.m_size; 
						} 
					} 
					 
					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); 
						 
						m_pDataSocket->AsyncSelect(); 
					} 
					break; 
			} 
		} 
		break; 
 
		// restart transfer 
		case TOK_REST: 
		{ 
			m_dwRestartOffset = atol(strArguments); 
			SendResponse("350 Restarting at %d.", m_dwRestartOffset); 
		} 
		break; 
 
		// get file size 
		case TOK_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; 
				} 
			} 
		} 
		break; 
		 
		// delete file 
		case TOK_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; 
			} 
		} 
		break; 
		 
		// remove directory 
		case TOK_RMD:  
		case TOK_XRMD:  
		{ 
			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; 
			} 
		} 
		break;	 
		 
		// create directory 
		case TOK_MKD:  
		case TOK_XMKD:  
		{ 
			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; 
			} 
		} 
		break; 
		 
		// rename file or directory (part 1) 
		case TOK_RNFR: 
		{ 
			CString strResult; 
			// is argument a valid filename ? 
			int nResult = CheckFileName(strArguments, FTP_RENAME, strResult); 
			if (nResult == ERROR_SUCCESS) 
			{ 
				m_strRenameFile = strResult; 
				m_bRenameFile = TRUE; 
				SendResponse("350 File exists, ready for destination name."); 
				break; 
			} 
			else 
			{ 
				// client wants to rename directory 
				nResult = CheckDirectory(strArguments, FTP_RENAME, strResult); 
				switch(nResult) 
				{ 
					case ERROR_SUCCESS: 
						m_strRenameFile = strResult; 
						m_bRenameFile = FALSE; 
						SendResponse("350 Directory exists, ready for destination name."); 
						break; 
					case ERROR_ACCESS_DENIED: 
						SendResponse("550 Permission denied."); 
						break; 
					default:  
						SendResponse("550 File/directory not found."); 
						break; 
				} 
			}	 
		} 
		break; 
 
		// rename file or directory (part 2) 
		case TOK_RNTO: 
		{ 
			if (m_strRenameFile.IsEmpty()) 
			{ 
				SendResponse("503 Bad sequence of commands."); 
				break; 
			} 
 
			CString strResult; 
			int nResult; 
		 
			// check destination filename/directory 
			if (m_bRenameFile) 
				nResult = CheckFileName(strArguments, FTP_RENAME, strResult); 
			else 
				nResult = CheckDirectory(strArguments, FTP_RENAME, strResult); 
 
			switch(nResult) 
			{ 
				case ERROR_SUCCESS: 
					SendResponse("550 %s already exists.", m_bRenameFile ? "File" : "Directory"); 
					break; 
				case ERROR_ACCESS_DENIED: 
					SendResponse("550 Permission denied."); 
					break; 
				default: 
					CString strRelativePath; 
					GetRelativePath(m_strRenameFile, strRelativePath); 
 
					// rename file/directory 
					if (!MoveFile(m_strRenameFile, strResult)) 
					{ 
						SendResponse("450 Internal error renaming the %s: \"%s\".", m_bRenameFile ? "file" : "directory", strRelativePath); 
					} 
					else 
					{ 
						SendResponse("250 %s \"%s\" renamed successfully.", m_bRenameFile ? "File" : "Directory", strRelativePath); 
					} 
					break; 
			} 
		} 
		break; 
 
		// abort transfer 
		case TOK_ABOR: 
		{ 
			if (m_pDataSocket) 
			{ 
				if (m_nStatus != STATUS_IDLE) 
				{ 
					SendResponse("426 Data connection closed."); 
				} 
				// destroy data connection 
				m_pThread->PostThreadMessage(WM_THREADMSG, 0, 0); 
			} 
			SendResponse("226 ABOR command successful."); 
			break; 
		}  
 
		// get system info 
		case TOK_SYST: 
			SendResponse("215 UNIX emulated by LQB's FTP Server."); 
			break; 
		 
		// close connection 
		case TOK_QUIT: 
		case TOK_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_THREADMSG, 1, 0); 
			break; 
		} 
 
		// display help 
		case TOK_HELP: 
			// if client did not specify a command, display all available commands 
			if (strArguments == "") 
			{ 
				CString strResponse = "214-The following commands are recognized:\r\n"; 
				// find command in command list 
				for (int i = TOK_ABOR; i < TOK_ERROR; i++) 
				{ 
					strResponse += commandList[i].m_pszName; 
					// four commands on one line 
					if ((i+1) % 4 == 0) 
						strResponse += "\r\n"; 
					else 
						strResponse += "\t"; 
				} 
				strResponse += "\r\n214 HELP command successful."; 
				SendResponse(strResponse); 
			} 
			else 
			{ 
				int nHelpCmd; 
				// find command in command list 
				for (nHelpCmd = TOK_ABOR; nHelpCmd < TOK_ERROR; nHelpCmd++) 
				{ 
					// found command ? 
					if (strArguments.CompareNoCase(commandList[nHelpCmd].m_pszName) == 0) 
					{ 
						break;			 
					} 
				} 
				if (nHelpCmd != TOK_ERROR) 
				{ 
					// show help about command 
					SendResponse("214 %s", commandList[nHelpCmd].m_pszDescription); 
				} 
				else 
				{ 
					SendResponse("501 Unknown command %s", strArguments); 
				} 
			} 
			break; 
 
		// dummy instruction 
		case TOK_NOOP: 
			SendResponse("200 OK"); 
			break; 
 
		default: 
			SendResponse("502 Command not implemented - Try HELP."); 
			break; 
	} 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* 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()) 
		{ 
			// temporary ignore FD_READ and FD_WRITE 
			m_pDataSocket->AsyncSelect(FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE); 
 
			// 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_dwRestartOffset = 0; 
	m_bPassiveMode = FALSE; 
} 
 
 
/********************************************************************/ 
/*																	*/ 
/* Function name : GetLocalPath										*/ 
/* Description   : Convert relative path to local path				*/ 
/*																	*/ 
/********************************************************************/ 
BOOL CControlSocket::GetLocalPath(LPCTSTR lpszRelativePath, CString &strLocalPath) 
{ 
	CStringList partList; 
	CString strSub; 
	int nCount=0; 
	BOOL bPathExists = TRUE; 
 
	// split path in parts 
	while(AfxExtractSubString(strSub, lpszRelativePath, nCount++, '/')) 
	{ 
		// get rid of insignificant dots 
		if (strSub != "..") 
		{ 
			strSub.TrimLeft('.'); 
			strSub.TrimRight('.'); 
		} 
 
		if (!strSub.IsEmpty()) 
			partList.AddTail(strSub); 
	} 
	 
	// search for home directory 
	for (int i=0; i do not go back further than root 
					if (!IsVirtualDirectory(strHomeDir)) 
					{ 
						// go back one level 
						int nPos = strHomeDir.ReverseFind('\\'); 
						if (nPos != -1) 
						{ 
							strCheckDir = strHomeDir.Left(nPos); 
						} 
					} 
					else 
					{ 
						strCheckDir = m_User.m_DirectoryArray[i].m_strDir; 
					} 
				} 
				else 
				{ 
					strCheckDir = strHomeDir + "\\" + strPart; 
				} 
			 
				// does directory exist ? 
				if (FileExists(strCheckDir, TRUE)) 
				{ 
					strHomeDir = strCheckDir; 
				} 
				else 
				// does file exist ? 
				if (FileExists(strCheckDir, FALSE)) 
				{ 
					strHomeDir = strCheckDir; 
				} 
				else 
				{ 
					BOOL bFound = FALSE; 
					// virtual directories exist only in the root 
					if (strHomeDir == m_User.m_DirectoryArray[i].m_strDir) 
					{ 
						// maybe it's a virtual directory 
						for (int j=0; j 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; 
 
		if (!bNamesOnly) 
		{ 
			// 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"; 
	} 
	 
	// if it's the root dir also show virtual directories 
	for (int i=0; i