// Creates a data connection using the given command socket, mode, and message from the client giving the port public DataSocket(FTPSocket ftp, bool passive, string message) { if (passive) { // Establishes a server for the client to connect to in passive mode listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(ftp.LocalIP, 0)); listener.Listen(1); var local = (IPEndPoint)listener.LocalEndPoint; var ip = local.Address.ToString().Replace('.', ','); var port = local.Port; ftp.Write("227 Entering Passive Mode ({0},{1},{2}).", ip, port / 256, port % 256); } else { // Connects to the clients data connection in active mode var match = Regex.Match(message, @"(\d+,\d+,\d+,\d+),(\d+),(\d+)"); address = IPAddress.Parse(match.Groups[1].Value.Replace(',', '.')); port = int.Parse(match.Groups[2].Value) * 256 + int.Parse(match.Groups[3].Value); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ftp.Write("200 PORT command successful. Consider using PASV."); } }
// Listens and responds to commands from the client public bool HandleCommand() { // Reads and extracts the command and arguments var input = ftp.Read(); var match = Regex.Match(input, @"([^ ]+)(?: (.+))?"); var command = ""; var arguments = ""; if (match.Groups[1].Success) { command = match.Groups[1].Value.ToUpper(); } if (match.Groups[2].Success) { arguments = match.Groups[2].Value; } // Stops if the client is not authenticated or authenticating if ((!user && command != "USER") || (!pass && command != "USER" && command != "PASS")) { ftp.Write("530 Please login with USER and PASS."); return(false); } switch (command) { // Moves the current path up 1 directory case "CDUP": path = Normalize(Path.Combine(".", path, "..")); ftp.Write("250 Directory successfully changed."); break; // Moves to the specified directory case "CWD": if (arguments.Length > 0) { var temp = Path.Combine(".", path, arguments); if (Directory.Exists(temp)) { path = Normalize(temp); ftp.Write("250 Directory successfully changed."); break; } } ftp.Write("550 Failed to change directory."); break; // Lists files in the current directory case "LIST": if (data == null) { ftp.Write("425 Use PASV or PORT first."); break; } ftp.Write("150 Here comes the directory listing."); data.SendDir(path); ftp.Write("226 Directory send OK."); data = null; break; // Accepts a password from the client case "PASS": ftp.Write("230 Login successful."); pass = true; break; // Sets up a passive mode data connection case "PASV": data = new DataSocket(ftp, true, null); break; // Sets up an active mode data connection case "PORT": data = new DataSocket(ftp, false, arguments); break; // Prints the working directory to the client case "PWD": ftp.Write("257 \"{0}\"", path); break; // Lets the client quit the connection case "QUIT": ftp.Write("221 Goodbye."); return(true); // Sends a file back to the client case "RETR": if (data == null) { ftp.Write("425 Use PASV or PORT first."); break; } var file = Path.GetRelativePath(".", Path.Combine(path, arguments)); if (!File.Exists(file)) { ftp.Write("550 Failed to open file."); break; } ftp.Write("150 Opening BINARY mode data connection for {0}", arguments); data.SendFile(file); ftp.Write("226 Transfer complete."); data = null; break; // Sets the file transfer encoding case "TYPE": if (arguments == "I") { ftp.Write("200 Switching to Binary mode."); break; } ftp.Write("200 Only Binary mode is supported."); break; // Accepts usernames for authentication case "USER": if (arguments == "ftp" || arguments == "anonymous") { ftp.Write("331 Please specify the password."); user = true; break; } ftp.Write("530 This FTP server is anonymous only."); break; // Handles unknown commands default: ftp.Write("200 Command not supported."); break; } return(false); }