PageRenderTime 40ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/MSNPSHARP_DEV/ProxyServer/FtpClient.cs

http://msnp-sharp.googlecode.com/
C# | 342 lines | 254 code | 4 blank | 84 comment | 29 complexity | 1f22caed738f54ad56fa8695538c6b4b MD5 | raw file
  1. /*
  2. Copyright ?2002, The KPD-Team
  3. All rights reserved.
  4. http://www.mentalis.org/
  5. Redistribution and use in source and binary forms, with or without
  6. modification, are permitted provided that the following conditions
  7. are met:
  8. - Redistributions of source code must retain the above copyright
  9. notice, this list of conditions and the following disclaimer.
  10. - Neither the name of the KPD-Team, nor the names of its contributors
  11. may be used to endorse or promote products derived from this
  12. software without specific prior written permission.
  13. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  14. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  15. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  16. FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  17. THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  18. INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  20. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  21. HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  22. STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  23. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  24. OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. using System;
  27. using System.Text;
  28. using System.Net;
  29. using System.Net.Sockets;
  30. using Org.Mentalis.Proxy;
  31. namespace Org.Mentalis.Proxy.Ftp {
  32. ///<summary>Relays FTP commands between a remote host and a local client.</summary>
  33. ///<remarks>This class supports the 'OPEN' command, 'USER user@host:port' and 'USER user@host port'.</remarks>
  34. public sealed class FtpClient : Client {
  35. ///<summary>Initializes a new instance of the FtpClient class.</summary>
  36. ///<param name="ClientSocket">The Socket connection between this proxy server and the local client.</param>
  37. ///<param name="Destroyer">The callback method to be called when this Client object disconnects from the local client and the remote server.</param>
  38. public FtpClient(Socket ClientSocket, DestroyDelegate Destroyer) : base(ClientSocket, Destroyer) {}
  39. ///<summary>Sends a welcome message to the client.</summary>
  40. public override void StartHandshake() {
  41. try {
  42. string ToSend = "220 Mentalis.org FTP proxy server ready.\r\n";
  43. ClientSocket.BeginSend(Encoding.ASCII.GetBytes(ToSend), 0, ToSend.Length, SocketFlags.None, new AsyncCallback(this.OnHelloSent), ClientSocket);
  44. } catch {
  45. Dispose();
  46. }
  47. }
  48. ///<summary>Called when the welcome message has been sent to the client.</summary>
  49. ///<param name="ar">The result of the asynchronous operation.</param>
  50. private void OnHelloSent(IAsyncResult ar) {
  51. try {
  52. if (ClientSocket.EndSend(ar) <= 0) {
  53. Dispose();
  54. return;
  55. }
  56. ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceiveCommand), ClientSocket);
  57. } catch {
  58. Dispose();
  59. }
  60. }
  61. ///<summary>Called when we have received some bytes from the client.</summary>
  62. ///<param name="ar">The result of the asynchronous operation.</param>
  63. private void OnReceiveCommand(IAsyncResult ar) {
  64. try {
  65. int Ret = ClientSocket.EndReceive(ar);
  66. string Command;
  67. if (Ret <= 0) {
  68. Dispose();
  69. return;
  70. }
  71. FtpCommand += Encoding.ASCII.GetString(Buffer, 0, Ret);
  72. if (FtpClient.IsValidCommand(FtpCommand)) {
  73. Command = FtpCommand;
  74. if (ProcessCommand(Command))
  75. DestinationSocket.BeginSend(Encoding.ASCII.GetBytes(Command), 0, Command.Length, SocketFlags.None, new AsyncCallback(this.OnCommandSent), DestinationSocket);
  76. FtpCommand = "";
  77. } else {
  78. ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceiveCommand), ClientSocket);
  79. }
  80. } catch {
  81. Dispose();
  82. }
  83. }
  84. ///<summary>Processes an FTP command, sent from the client.</summary>
  85. ///<param name="Command">The command to process.</param>
  86. ///<returns>True if the command may be sent to the server, false otherwise.</returns>
  87. private bool ProcessCommand(string Command) {
  88. try {
  89. int Ret = Command.IndexOf(' ');
  90. if (Ret < 0)
  91. Ret = Command.Length;
  92. switch (Command.Substring(0, Ret).ToUpper().Trim()) {
  93. case "OPEN":
  94. ConnectTo(ParseIPPort(Command.Substring(Ret + 1)));
  95. break;
  96. case "USER":
  97. Ret = Command.IndexOf('@');
  98. if (Ret < 0) {
  99. return true;
  100. } else {
  101. User = Command.Substring(0, Ret).Trim() + "\r\n";
  102. ConnectTo(ParseIPPort(Command.Substring(Ret + 1)));
  103. }
  104. break;
  105. case "PORT":
  106. ProcessPortCommand(Command.Substring(5).Trim());
  107. break;
  108. case "PASV":
  109. DataForward = new FtpDataConnection();
  110. DataForward.ProcessPasv(this);
  111. return true;
  112. default:
  113. return true;
  114. }
  115. return false;
  116. } catch {
  117. Dispose();
  118. return false;
  119. }
  120. }
  121. ///<summary>Processes a PORT command, sent from the client.</summary>
  122. ///<param name="Input">The parameters of the PORT command.</param>
  123. private void ProcessPortCommand(string Input) {
  124. try {
  125. string [] Parts = Input.Split(',');
  126. if (Parts.Length == 6) {
  127. DataForward = new FtpDataConnection();
  128. string Reply = DataForward.ProcessPort(new IPEndPoint(IPAddress.Parse(String.Join(".", Parts, 0, 4)), int.Parse(Parts[4]) * 256 + int.Parse(Parts[5])));
  129. DestinationSocket.BeginSend(Encoding.ASCII.GetBytes(Reply), 0, Reply.Length, SocketFlags.None, new AsyncCallback(this.OnCommandSent), DestinationSocket);
  130. }
  131. } catch {
  132. Dispose();
  133. }
  134. }
  135. ///<summary>Parses an IP address and port from a specified input string.</summary>
  136. ///<remarks>The input string is of the following form:<br> <c>HOST:PORT</c></br><br><em>or</em></br><br> <c>HOST PORT</c></br></remarks>
  137. ///<param name="Input">The string to parse.</param>
  138. ///<returns>An instance of the IPEndPoint class if successful, null otherwise.</returns>
  139. private IPEndPoint ParseIPPort(string Input) {
  140. Input = Input.Trim();
  141. int Ret = Input.IndexOf(':');
  142. if (Ret < 0)
  143. Ret = Input.IndexOf(' ');
  144. try {
  145. if (Ret > 0) {
  146. return new IPEndPoint(Dns.GetHostEntry(Input.Substring(0, Ret)).AddressList[0], int.Parse(Input.Substring(Ret + 1)));
  147. } else {
  148. return new IPEndPoint(Dns.GetHostEntry(Input).AddressList[0], 21);
  149. }
  150. } catch {
  151. return null;
  152. }
  153. }
  154. ///<summary>Connects to the specified endpoint.</summary>
  155. ///<param name="RemoteServer">The IPEndPoint to connect to.</param>
  156. ///<exception cref="SocketException">There was an error connecting to the specified endpoint</exception>
  157. private void ConnectTo(IPEndPoint RemoteServer) {
  158. if (DestinationSocket != null) {
  159. try {
  160. DestinationSocket.Shutdown(SocketShutdown.Both);
  161. } catch {
  162. } finally {
  163. DestinationSocket.Close();
  164. }
  165. }
  166. try {
  167. DestinationSocket = new Socket(RemoteServer.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  168. DestinationSocket.BeginConnect(RemoteServer, new AsyncCallback(this.OnRemoteConnected), DestinationSocket);
  169. } catch {
  170. throw new SocketException();
  171. }
  172. }
  173. ///<summary>Called when we're connected to the remote FTP server.</summary>
  174. ///<param name="ar">The result of the asynchronous operation.</param>
  175. private void OnRemoteConnected(IAsyncResult ar) {
  176. try {
  177. DestinationSocket.EndConnect(ar);
  178. ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceiveCommand), ClientSocket);
  179. if (User.Equals(""))
  180. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnReplyReceived), DestinationSocket);
  181. else
  182. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnIgnoreReply), DestinationSocket);
  183. } catch {
  184. Dispose();
  185. }
  186. }
  187. ///<summary>Called when we receive a reply from the FTP server that should be ignored.</summary>
  188. ///<param name="ar">The result of the asynchronous operation.</param>
  189. private void OnIgnoreReply(IAsyncResult ar) {
  190. try {
  191. int Ret = DestinationSocket.EndReceive(ar);
  192. if (Ret <= 0) {
  193. Dispose();
  194. return;
  195. }
  196. FtpReply += Encoding.ASCII.GetString(RemoteBuffer, 0, Ret);
  197. if (FtpClient.IsValidReply(FtpReply)) {
  198. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnReplyReceived), DestinationSocket);
  199. DestinationSocket.BeginSend(Encoding.ASCII.GetBytes(User), 0, User.Length, SocketFlags.None, new AsyncCallback(this.OnCommandSent), DestinationSocket);
  200. } else {
  201. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnIgnoreReply), DestinationSocket);
  202. }
  203. } catch {
  204. Dispose();
  205. }
  206. }
  207. ///<summary>Called when an FTP command has been successfully sent to the FTP server.</summary>
  208. ///<param name="ar">The result of the asynchronous operation.</param>
  209. private void OnCommandSent(IAsyncResult ar) {
  210. try {
  211. if (DestinationSocket.EndSend(ar) <= 0) {
  212. Dispose();
  213. return;
  214. }
  215. ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceiveCommand), ClientSocket);
  216. } catch {
  217. Dispose();
  218. }
  219. }
  220. ///<summary>Called when we receive a reply from the FTP server.</summary>
  221. ///<param name="ar">The result of the asynchronous operation.</param>
  222. private void OnReplyReceived(IAsyncResult ar) {
  223. try {
  224. int Ret = DestinationSocket.EndReceive(ar);
  225. if (Ret <= 0) {
  226. Dispose();
  227. return;
  228. }
  229. if (DataForward != null && DataForward.ExpectsReply) {
  230. if (!DataForward.ProcessPasvReplyRecv(Encoding.ASCII.GetString(RemoteBuffer, 0, Ret)))
  231. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnReplyReceived), DestinationSocket);
  232. } else {
  233. ClientSocket.BeginSend(RemoteBuffer, 0, Ret, SocketFlags.None, new AsyncCallback(this.OnReplySent), ClientSocket);
  234. }
  235. } catch {
  236. Dispose();
  237. }
  238. }
  239. ///<summary>Called when the reply from the FTP server has been sent to the local FTP client.</summary>
  240. ///<param name="ar">The result of the asynchronous operation.</param>
  241. private void OnReplySent(IAsyncResult ar) {
  242. try {
  243. int Ret = ClientSocket.EndSend(ar);
  244. if (Ret <= 0) {
  245. Dispose();
  246. return;
  247. }
  248. DestinationSocket.BeginReceive(RemoteBuffer, 0, RemoteBuffer.Length, SocketFlags.None, new AsyncCallback(this.OnReplyReceived), DestinationSocket);
  249. } catch {
  250. Dispose();
  251. }
  252. }
  253. ///<summary>Sends a string to the local FTP client.</summary>
  254. ///<param name="Command">The result of the asynchronous operation.</param>
  255. internal void SendCommand(string Command) {
  256. ClientSocket.BeginSend(Encoding.ASCII.GetBytes(Command), 0, Command.Length, SocketFlags.None, new AsyncCallback(this.OnReplySent), ClientSocket);
  257. }
  258. ///<summary>Checks whether a specified command is a complete FTP command or not.</summary>
  259. ///<param name="Command">A string containing the command to check.</param>
  260. ///<returns>True if the command is complete, false otherwise.</returns>
  261. internal static bool IsValidCommand(string Command) {
  262. return (Command.IndexOf("\r\n") >= 0);
  263. }
  264. ///<summary>Checks whether a specified reply is a complete FTP reply or not.</summary>
  265. ///<param name="Input">A string containing the reply to check.</param>
  266. ///<returns>True is the reply is complete, false otherwise.</returns>
  267. internal static bool IsValidReply(string Input) {
  268. string [] Lines = Input.Split('\n');
  269. try {
  270. if (Lines[Lines.Length - 2].Trim().Substring(3, 1).Equals(" "))
  271. return true;
  272. } catch {}
  273. return false;
  274. }
  275. ///<summary>Gets or sets a property that can be used to store the received FTP command.</summary>
  276. ///<value>A string that can be used to store the received FTP command.</value>
  277. private string FtpCommand {
  278. get {
  279. return m_FtpCommand;
  280. }
  281. set {
  282. m_FtpCommand = value;
  283. }
  284. }
  285. ///<summary>Gets or sets a property that can be used to store the received FTP reply.</summary>
  286. ///<value>A string that can be used to store the received FTP reply.</value>
  287. private string FtpReply {
  288. get {
  289. return m_FtpReply;
  290. }
  291. set {
  292. m_FtpReply = value;
  293. }
  294. }
  295. ///<summary>Gets or sets a string containing the logged on username.</summary>
  296. ///<value>A string containing the logged on username.</value>
  297. private string User {
  298. get {
  299. return m_User;
  300. }
  301. set {
  302. m_User = value;
  303. }
  304. }
  305. ///<summary>Gets or sets the dataconnection object used to transmit files and other data from and to the FTP server.</summary>
  306. ///<value>An FtpDataConnection object that's used to transmit files and other data from and to the FTP server.</value>
  307. internal FtpDataConnection DataForward {
  308. get {
  309. return m_DataForward;
  310. }
  311. set {
  312. m_DataForward = value;
  313. }
  314. }
  315. ///<summary>Returns text information about this FtpClient object.</summary>
  316. ///<returns>A string representing this FtpClient object.</returns>
  317. public override string ToString() {
  318. try {
  319. return "FTP connection from " + ((IPEndPoint)ClientSocket.RemoteEndPoint).Address.ToString() + " to " + ((IPEndPoint)DestinationSocket.RemoteEndPoint).Address.ToString();
  320. } catch {
  321. return "Incoming FTP connection";
  322. }
  323. }
  324. // private variables
  325. /// <summary>Holds the value of the User property.</summary>
  326. private string m_User = "";
  327. /// <summary>Holds the value of the FtpCommand property.</summary>
  328. private string m_FtpCommand = "";
  329. /// <summary>Holds the value of the FtpReply property.</summary>
  330. private string m_FtpReply = "";
  331. /// <summary>Holds the value of the DataForward property.</summary>
  332. private FtpDataConnection m_DataForward;
  333. }
  334. }