/// <summary> /// Control thread /// TODO: separate the functionalities into functions to reduce maintenance cost /// </summary> private void WorkerThread() { FtpCommand command; FtpState state = FtpState.WaitUser; string tempName = null; // store old name for "rename from" command string response; int timeout = 0; try { while (m_IsAlive) { if (m_IsDisposed) { break; } else if (!m_Welcomed) { response = "220 MyFTP " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + " Server [" + m_HostIp.ToString() + "] \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); m_Welcomed = true; } else if (m_CommandSocket == null) { Logging.Print("Session socket has been closed."); break; } else if (m_CommandSocket.Poll(1000, SelectMode.SelectRead)) { if (m_CommandSocket.Available == 0) { Logging.Print("REMOTE DISCONNECT " + m_CommandSocket.RemoteEndPoint.ToString()); m_IsAlive = false; break; } // read until find the end of a line command = FtpCommandCreator.Create(ReadCommand()); if (command == null) { break; } else { switch (command.Type) { case FtpCommandType.User: response = "331 " + command.Content + " login ok \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); state = FtpState.WaitPwd; tempName = command.Content; break; case FtpCommandType.Pass: if (state != FtpState.WaitPwd) { response = "332 Need Account for Login.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { UserAuthenticatorArgs args = new UserAuthenticatorArgs(tempName, command.Content); FtpListener.RaiseEvent(this, args); if (args.Result == UserAuthenticationResult.Approved) { User.UserName = tempName; User.PassWord = command.Content; response = "230 access granted for " + User.UserName + ", restrictions apply. \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); state = FtpState.WaitCommand; break; } else { response = "530 Login incorrect. \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); state = FtpState.WaitCommand; break; } } case FtpCommandType.Cwd: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitUser) { response = "332 Need Account for Login.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { if (command.Content == null) { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethodsEx.Ftp.ChangeDirectory, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Quit: m_IsAlive = false; CloseDataChannel(); response = "221 Goodbye.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); m_CommandSocket.Close(); m_CommandSocket = null; break; case FtpCommandType.Pasv: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { CloseDataChannel(); m_DataThread = new Thread(PassiveThread); m_DataThread.Start(); m_DataReadyEvent.WaitOne(); // wait until port has been successfully assigned byte[] addrs = m_HostIp.GetAddressBytes(); int upper = m_HostPort / 256; int lower = m_HostPort % 256; if (addrs.Length == 4) //IPv4 { response = "227 Entering Passive Mode ("; foreach (int i in addrs) { response += i.ToString() + ","; } response += upper.ToString() + "," + lower.ToString() + ")\r\n"; } else // currently do not support IPv6 { throw new NotImplementedException("currently does not support IPv6"); } m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Type: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "200 Command OK.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.List: if (!m_DataModeON) { response = "425 Use PORT or PASV first.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "150 Opening UTF8 mode data connection for *** \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.ListDirectoryDetails, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.NList: if (!m_DataModeON) { response = "425 Use PORT or PASV first.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "150 Opening UTF8 mode data connection for *** \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.ListDirectory, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Port: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand && command.Content != null) { CloseDataChannel(); string[] epad = command.Content.Split(new char[] { ',' }); if (epad.Length != 6) { response = "500 Invalid PORT command.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); } else { try { m_ClientIp = IPAddress.Parse(epad[0] + "." + epad[1] + "." + epad[2] + "." + epad[3]); m_ClientPort = Int32.Parse(epad[4]) * 256 + Int32.Parse(epad[5]); if (m_ClientPort <= 0 || m_ClientPort > 65535) { response = "500 Invalid PORT command.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); } else { m_ActThreadW = true; m_DataThread = new Thread(ActiveThread); m_DataThread.Start(); response = "200 PORT command successful.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); } } catch (Exception) { response = "500 Invalid PORT command.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); } } break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Sys: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "215 System Type: EMIC\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Feature: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "211-Features:\r\n SIZE\r\n211 End\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Pwd: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { response = "257 \"" + m_CurrentDirectory.GetNetPath() + "\" is the current directory. \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Retr: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { if (!m_DataModeON) { response = "425 Use PORT or PASV first.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } response = "150 Opening BINARY mode data connection for " + command.Content + ". \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.DownloadFile, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Opts: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { if (command.Content == "utf8") { response = "213 Always in utf8 mode \r\n"; } else { response = "550 Requested action not taken. Mode not support. \r\n"; } m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Size: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.GetFileSize, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Store: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { if (!m_DataModeON) { response = "425 Use PORT or PASV first.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } response = "150 Opening BINARY mode data connection for " + command.Content + ". \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.UploadFile, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Noop: response = "200 NOOP command successful. \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; case FtpCommandType.Delete: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.DeleteFile, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.MkDir: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.MakeDirectory, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Rmd: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethods.Ftp.RemoveDirectory, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Rnfr: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethodsEx.Ftp.RenameFrom, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } case FtpCommandType.Rnto: if (state == FtpState.WaitPwd) { response = "331 Need Password.\r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } else if (state == FtpState.WaitCommand) { FtpListenerRequest request = new FtpListenerRequest(WebRequestMethodsEx.Ftp.RenameTo, m_CurrentDirectory.Combine(command.Content).GetNetPath(), this); FtpListenerContext context = new FtpListenerContext(this, request); m_SendContext.AddContext(context); break; } else { response = m_BadSequence; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } default: response = "502 Command not implemented. \r\n"; if (state == FtpState.WaitPwd) { state = FtpState.WaitUser; } m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); break; } } // reset time out timeout = 0; } else if (m_CommandSocket.Poll(1000, SelectMode.SelectError)) { Logging.Print("Disconnected unproperly."); break; } else { if (!m_Welcomed) { response = "220 MyFTP " + typeof(FtpListenerSession).Assembly.GetName().Version.ToString() + " Server [" + m_HostIp.ToString() + "] \r\n"; m_CommandSocket.Send(Encoding.UTF8.GetBytes(response)); m_Welcomed = true; } else { if (SessionTimeOut != -1) { if (timeout > SessionTimeOut) { m_IsAlive = false; Logging.Print("Connection time out."); break; } timeout += 50; } Thread.Sleep(50); } } } } catch (SocketException se) { Logging.Print("Control Socket Exception : " + se.ErrorCode.ToString()); } catch (IOException ioe) { Logging.Print("IOException: " + ioe.Message); } finally { m_IsAlive = false; if (m_CommandSocket != null) { m_CommandSocket.Close(); m_CommandSocket = null; } if (m_ListenSocket != null) { m_ListenSocket.Close(); m_CommandSocket = null; } Logging.Print("Client Disconnected."); } }