using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.IO; namespace Tor.Controller { /// /// A class containing methods for interacting with a control connection for a tor application. /// internal sealed class Connection : IDisposable { private readonly static string EOL = "\r\n"; private readonly Client client; private volatile bool disposed; private StreamReader reader; private Socket socket; private NetworkStream stream; /// /// Initializes a new instance of the class. /// /// The client hosting the control connection. public Connection(Client client) { this.client = client; this.disposed = false; this.reader = null; this.socket = null; this.stream = null; } /// /// Finalizes an instance of the class. /// ~Connection() { Dispose(false); } #region System.IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. private void Dispose(bool disposing) { if (disposed) return; if (disposing) { if (reader != null) { reader.Dispose(); reader = null; } if (stream != null) { stream.Dispose(); stream = null; } if (socket != null) { if (socket.Connected) socket.Shutdown(SocketShutdown.Both); socket.Dispose(); socket = null; } disposed = true; } } #endregion /// /// Authenticates the connection by sending the password to the control port. /// /// The password used for authentication. /// true if the authentication succeeds; otherwise, false. public bool Authenticate(string password) { if (disposed) throw new ObjectDisposedException("this"); if (password == null) password = ""; if (Write("authenticate \"{0}\"", password)) { ConnectionResponse response = Read(); if (response.Success) return true; } return false; } /// /// Connects to the control port hosted by the client. /// /// true if the connection succeeds; otherwise, false. public bool Connect() { if (disposed) throw new ObjectDisposedException("this"); try { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(client.GetClientAddress(), client.GetControlPort()); stream = new NetworkStream(socket, false); stream.ReadTimeout = 2000; reader = new StreamReader(stream); return true; } catch { if (reader != null) { reader.Dispose(); reader = null; } if (stream != null) { stream.Dispose(); stream = null; } if (socket != null) { if (socket.Connected) socket.Shutdown(SocketShutdown.Both); socket.Dispose(); socket = null; } return false; } } /// /// Reads a response buffer from the control connection. This method is blocking with a receive timeout of 500ms. /// /// A containing the response information. public ConnectionResponse Read() { if (disposed) throw new ObjectDisposedException("this"); if (socket == null || stream == null || reader == null) return new ConnectionResponse(StatusCode.Unknown); try { string line = reader.ReadLine(); if (line == null) return new ConnectionResponse(StatusCode.Unknown); if (line.Length < 3) return new ConnectionResponse(StatusCode.Unknown); int code; if (!int.TryParse(line.Substring(0, 3), out code)) return new ConnectionResponse(StatusCode.Unknown); line = line.Substring(3); if (line.Length == 0) return new ConnectionResponse((StatusCode)code, new List() { "" }); if (line[0] != '+' && line[0] != '-') { if (line[0] == ' ') line = line.Substring(1); return new ConnectionResponse((StatusCode)code, new List() { line }); } char id = line[0]; List responses = new List(); responses.Add(line.Substring(1)); try { for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) { string temp1 = line.Trim(); string temp2 = temp1; if (temp1.Length == 0) continue; if (id == '-' && temp2.Length > 3 && temp2[3] == ' ') break; if (temp1.Length > 3 && id != '+') temp1 = temp1.Substring(4); responses.Add(temp1); if (id == '+' && ".".Equals(temp1)) break; } } catch { } return new ConnectionResponse((StatusCode)code, responses); } catch { return new ConnectionResponse(StatusCode.Unknown); } } /// /// Writes a command to the connection and flushes the buffer to the control port. /// /// The command to write to the connection. /// true if the command is dispatched successfully; otherwise, false. public bool Write(string command) { if (command == null) throw new ArgumentNullException("command"); if (!command.EndsWith(EOL)) command += EOL; return Write(Encoding.ASCII.GetBytes(command)); } /// /// Writes a command to the connection and flushes the buffer to the control port. /// /// The command to write to the connection. /// An optional collection of parameters to serialize into the command. /// true if the command is dispatched successfully; otherwise, false. public bool Write(string command, params object[] parameters) { if (command == null) throw new ArgumentNullException("command"); command = string.Format(command, parameters); if (!command.EndsWith(EOL)) command += EOL; return Write(Encoding.ASCII.GetBytes(command)); } /// /// Writes a command to the connection and flushes the buffer to the control port. /// /// The buffer containing the command data. /// true if the command is dispatched successfully; otherwise, false. public bool Write(byte[] buffer) { if (disposed) throw new ObjectDisposedException("this"); if (buffer == null || buffer.Length == 0) throw new ArgumentNullException("buffer"); if (socket == null || stream == null || reader == null) return false; try { stream.Write(buffer, 0, buffer.Length); stream.Flush(); return true; } catch { return false; } } } }