using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using Tor.Config; using System.IO; using Tor.Controller; using Tor.Helpers; using Tor.IO; using System.Threading; using MarketData; namespace Tor { /// /// A class linked to a running tor application process, and provides methods and properties for interacting with the tor service. /// public sealed class Client : MarshalByRefObject, IDisposable { private readonly static Version minimumSupportedVersion = new Version(0, 2, 0, 9); private readonly ClientCreateParams createParams; private readonly ClientRemoteParams remoteParams; private readonly object synchronize; private Configuration configuration; private Control controller; private Events.Events events; private volatile bool disposed; private Logging.Logging logging; private Process process; private Proxy.Proxy proxy; private Status.Status status; /// /// Initializes a new instance of the class. /// /// The parameters used when creating the client. private Client(ClientCreateParams createParams) { this.createParams = createParams; this.disposed = false; this.process = null; this.remoteParams = null; this.synchronize = new object(); this.Start(); } /// /// Initializes a new instance of the class. /// /// The parameters used when connecting to the client. private Client(ClientRemoteParams remoteParams) { this.createParams = null; this.disposed = false; this.process = null; this.remoteParams = remoteParams; this.synchronize = new object(); this.Start(); } /// /// Finalizes an instance of the class. /// ~Client() { Dispose(false); } #region Properties /// /// Gets an object containing configuration values associated with the tor application. /// public Configuration Configuration { get { return configuration; } } /// /// Gets an object which can be used for performing control operations against the tor application. /// public Control Controller { get { return controller; } } /// /// Gets a value indicating whether the client is configured to a remote tor application. /// public bool IsRemote { get { return remoteParams != null; } } /// /// Gets a value indicating whether the tor application is still running for this client. /// public bool IsRunning { get { if (IsRemote) return true; lock (synchronize) return process != null && !process.HasExited; } } /// /// Gets an object which can be used to receive log messages from the tor client. /// public Logging.Logging Logging { get { return logging; } } /// /// Gets an object which manages the hosted HTTP proxy and can be used to create an object instance. /// public Proxy.Proxy Proxy { get { return proxy; } } /// /// Gets an object which provides methods and properties for determining the status of the tor network service. /// public Status.Status Status { get { return status; } } /// /// Gets an object which can be used to monitor for events within the tor service. /// internal Events.Events Events { get { return events; } } /// /// Gets the minimum supported tor version number. The version number is checked after being launched or connected, and will raise an /// exception if the minimum version number is not satisfied. /// public static Version MinimumSupportedVersion { get { return minimumSupportedVersion; } } #endregion #region Events /// /// Occurs when the client has been shutdown, either from manual shutdown or by forcible means. /// public event EventHandler Shutdown; #endregion #region System.Diagnostics.Process /// /// Called when the process has exited. /// /// The sender. /// The instance containing the event data. private void OnHandleProcessExited(object sender, EventArgs e) { Stop(true); } #endregion #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; Stop(false); disposed = true; } #endregion /// /// Creates a new object instance and attempts to launch the tor application executable. /// /// The parameters used when creating the client. /// A object instance. public static Client Create(ClientCreateParams createParams) { if (createParams == null) throw new ArgumentNullException("createParams"); return new Client(createParams); } /// /// Creates a new object instance configured to connect to a remotely hosted tor application executable. /// /// The parameters used when connecting to the client. /// A object instance. public static Client CreateForRemote(ClientRemoteParams remoteParams) { if (remoteParams == null) throw new ArgumentNullException("remoteParams"); return new Client(remoteParams); } /// /// Gets the address hosting the tor application. /// /// A containing the host address. internal string GetClientAddress() { if (IsRemote) return remoteParams.Address; else return "127.0.0.1"; } /// /// Gets the configurations from the tor application by dispatching the getconf command. /// private void GetClientConfigurations() { List configurations = ReflectionHelper.GetEnumeratorAttributes(attr => attr.Name); GetConfCommand command = new GetConfCommand(configurations); GetConfResponse response = command.Dispatch(this); if (!response.Success) throw new TorException("The client failed to retrieve configuration values from the tor application (check your control port and password)"); foreach (KeyValuePair value in response.Values) { string key = value.Key; string val = value.Value; configuration.SetValueDirect(key, val); } Version version = status.Version; Version empty = new Version(); if (empty < version && version < minimumSupportedVersion) { Dispose(); throw new TorException("This version of tor is not supported, please use version " + minimumSupportedVersion + " or higher"); } } /// /// Gets the control password to use in the control connection of the hosted tor application, based on /// the parameters supplied. /// /// A containing the control port password. internal string GetControlPassword() { if (IsRemote) return remoteParams.ControlPassword ?? ""; else return createParams.ControlPassword ?? ""; } /// /// Gets the control port of the hosted tor application based on the parameters supplied. /// /// A containing the control port number. internal int GetControlPort() { if (IsRemote) return remoteParams.ControlPort; else return createParams.ControlPort; } /// /// Gets a for the running client. This method will establish a connection to the specified /// host address and port number, and function as an intermediary for communications. /// /// A object instance connected to the specified host address and port through the tor network. public System.IO.Stream GetStream(string host, int port) { return new Socks5Stream(this, host, port); } /// /// Starts the tor application executable using the provided creation parameters. /// private void Start() { if (createParams != null) createParams.ValidateParameters(); else remoteParams.ValidateParameters(); if (createParams != null) { lock (synchronize) { ProcessStartInfo psi; if (process != null && !process.HasExited) return; psi = new ProcessStartInfo(createParams.Path); psi.Arguments = createParams.ToString(); psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.WindowStyle = ProcessWindowStyle.Hidden; if(createParams.CaptureTorConsoleOutput)psi.RedirectStandardOutput = true; psi.WorkingDirectory = Path.GetDirectoryName(createParams.Path); try { process = new Process(); if(createParams.CaptureTorConsoleOutput)process.OutputDataReceived += OutputDataReceived; process.EnableRaisingEvents = true; process.Exited += new EventHandler(OnHandleProcessExited); process.StartInfo = psi; if (!process.Start()) { process.Dispose(); process = null; throw new TorException("The tor application process failed to launch"); } if(createParams.CaptureTorConsoleOutput)process.BeginOutputReadLine(); } catch (Exception exception) { throw new TorException("The tor application process failed to launch", exception); } } } Thread.Sleep(500); lock (synchronize) { configuration = new Configuration(this); controller = new Control(this); logging = new Logging.Logging(this); proxy = new Proxy.Proxy(this); status = new Status.Status(this); events = new Events.Events(this); events.Start((Action)delegate { configuration.Start(); status.Start(); GetClientConfigurations(); }); } } private void OutputDataReceived(object sender, DataReceivedEventArgs dataReceivedEventArgs) { if (null==dataReceivedEventArgs || null==dataReceivedEventArgs.Data)return; String strData = dataReceivedEventArgs.Data.ToString(); if(null==strData || strData.Contains("New control connection opened" ) )return; MarketData.MDTrace.WriteLine(LogLevel.DEBUG,strData); } /// /// Shuts down the tor application process and releases the associated components of the class. /// /// A value indicating whether the shutdown is being performed after the process has exited. private void Stop(bool exited) { if (disposed && !exited) return; lock (synchronize) { if (!IsRemote && process != null) { if (exited) { process.Dispose(); process = null; } else { SignalHaltCommand command = new SignalHaltCommand(); Response response = command.Dispatch(this); if (response.Success) return; process.Kill(); process.Dispose(); process = null; } } if (proxy != null) { proxy.Dispose(); proxy = null; } if (events != null) { events.Dispose(); events = null; } configuration = null; controller = null; logging = null; status = null; if (Shutdown != null) Shutdown(this, EventArgs.Empty); } } } }