Files
Tor/TorWebClient/TorClient/Client.cs

434 lines
15 KiB
C#

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;
namespace Tor
{
/// <summary>
/// A class linked to a running tor application process, and provides methods and properties for interacting with the tor service.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
/// </summary>
/// <param name="createParams">The parameters used when creating the client.</param>
private Client(ClientCreateParams createParams)
{
this.createParams = createParams;
this.disposed = false;
this.process = null;
this.remoteParams = null;
this.synchronize = new object();
this.Start();
}
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
/// </summary>
/// <param name="remoateParams">The parameters used when connecting to the client.</param>
private Client(ClientRemoteParams remoteParams)
{
this.createParams = null;
this.disposed = false;
this.process = null;
this.remoteParams = remoteParams;
this.synchronize = new object();
this.Start();
}
/// <summary>
/// Finalizes an instance of the <see cref="Client"/> class.
/// </summary>
~Client()
{
Dispose(false);
}
#region Properties
/// <summary>
/// Gets an object containing configuration values associated with the tor application.
/// </summary>
public Configuration Configuration
{
get { return configuration; }
}
/// <summary>
/// Gets an object which can be used for performing control operations against the tor application.
/// </summary>
public Control Controller
{
get { return controller; }
}
/// <summary>
/// Gets a value indicating whether the client is configured to a remote tor application.
/// </summary>
public bool IsRemote
{
get { return remoteParams != null; }
}
/// <summary>
/// Gets a value indicating whether the tor application is still running for this client.
/// </summary>
public bool IsRunning
{
get { if (IsRemote) return true; lock (synchronize) return process != null && !process.HasExited; }
}
/// <summary>
/// Gets an object which can be used to receive log messages from the tor client.
/// </summary>
public Logging.Logging Logging
{
get { return logging; }
}
/// <summary>
/// Gets an object which manages the hosted HTTP proxy and can be used to create an <see cref="IWebProxy"/> object instance.
/// </summary>
public Proxy.Proxy Proxy
{
get { return proxy; }
}
/// <summary>
/// Gets an object which provides methods and properties for determining the status of the tor network service.
/// </summary>
public Status.Status Status
{
get { return status; }
}
/// <summary>
/// Gets an object which can be used to monitor for events within the tor service.
/// </summary>
internal Events.Events Events
{
get { return events; }
}
/// <summary>
/// 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.
/// </summary>
public static Version MinimumSupportedVersion
{
get { return minimumSupportedVersion; }
}
#endregion
#region Events
/// <summary>
/// Occurs when the client has been shutdown, either from manual shutdown or by forcible means.
/// </summary>
public event EventHandler Shutdown;
#endregion
#region System.Diagnostics.Process
/// <summary>
/// Called when the process has exited.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnHandleProcessExited(object sender, EventArgs e)
{
Stop(true);
}
#endregion
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
if (disposed)
return;
Stop(false);
disposed = true;
}
#endregion
/// <summary>
/// Creates a new <see cref="Client"/> object instance and attempts to launch the tor application executable.
/// </summary>
/// <param name="createParams">The parameters used when creating the client.</param>
/// <returns>A <see cref="Client"/> object instance.</returns>
public static Client Create(ClientCreateParams createParams)
{
if (createParams == null)
throw new ArgumentNullException("createParams");
return new Client(createParams);
}
/// <summary>
/// Creates a new <see cref="Client"/> object instance configured to connect to a remotely hosted tor application executable.
/// </summary>
/// <param name="remoateParams">The parameters used when connecting to the client.</param>
/// <returns>A <see cref="Client"/> object instance.</returns>
public static Client CreateForRemote(ClientRemoteParams remoteParams)
{
if (remoteParams == null)
throw new ArgumentNullException("remoteParams");
return new Client(remoteParams);
}
/// <summary>
/// Gets the address hosting the tor application.
/// </summary>
/// <returns>A <see cref="System.String"/> containing the host address.</returns>
internal string GetClientAddress()
{
if (IsRemote)
return remoteParams.Address;
else
return "127.0.0.1";
}
/// <summary>
/// Gets the configurations from the tor application by dispatching the <c>getconf</c> command.
/// </summary>
private void GetClientConfigurations()
{
List<string> configurations = ReflectionHelper.GetEnumeratorAttributes<ConfigurationNames, ConfigurationAssocAttribute, string>(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<string, string> 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");
}
}
/// <summary>
/// Gets the control password to use in the control connection of the hosted tor application, based on
/// the parameters supplied.
/// </summary>
/// <returns>A <see cref="System.String"/> containing the control port password.</returns>
internal string GetControlPassword()
{
if (IsRemote)
return remoteParams.ControlPassword ?? "";
else
return createParams.ControlPassword ?? "";
}
/// <summary>
/// Gets the control port of the hosted tor application based on the parameters supplied.
/// </summary>
/// <returns>A <see cref="System.Int32"/> containing the control port number.</returns>
internal int GetControlPort()
{
if (IsRemote)
return remoteParams.ControlPort;
else
return createParams.ControlPort;
}
/// <summary>
/// Gets a <see cref="System.IO.Stream"/> 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.
/// </summary>
/// <returns>A <see cref="System.IO.Stream"/> object instance connected to the specified host address and port through the tor network.</returns>
public System.IO.Stream GetStream(string host, int port)
{
return new Socks5Stream(this, host, port);
}
/// <summary>
/// Starts the tor application executable using the provided creation parameters.
/// </summary>
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;
psi.RedirectStandardOutput = true;
psi.WorkingDirectory = Path.GetDirectoryName(createParams.Path);
try
{
process = new Process();
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");
}
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 e)
{
Console.WriteLine(e.Data);
}
/// <summary>
/// Shuts down the tor application process and releases the associated components of the class.
/// </summary>
/// <param name="exited">A value indicating whether the shutdown is being performed after the process has exited.</param>
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);
}
}
}
}