Initial project commit
This commit is contained in:
382
TorControlLibrary/ControlConnection.cs
Normal file
382
TorControlLibrary/ControlConnection.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using TorControlLibrary.Responses;
|
||||
using TorControlLibrary.Exceptions;
|
||||
|
||||
namespace TorControlLibrary
|
||||
{
|
||||
public class ControlConnection
|
||||
{
|
||||
public ControlConnection()
|
||||
{
|
||||
Host = new IPEndPoint(IPAddress.Loopback, 9051);
|
||||
Timeout = new TimeSpan(0, 0, 90);
|
||||
HashedControlPassword = String.Empty;
|
||||
}
|
||||
public void Connect(IPEndPoint host)
|
||||
{
|
||||
Host = host;
|
||||
Connect();
|
||||
}
|
||||
public void Connect(String authenticationPassword)
|
||||
{
|
||||
HashedControlPassword = authenticationPassword;
|
||||
Connect();
|
||||
}
|
||||
public void Connect(IPEndPoint host, String hashedControlPassword)
|
||||
{
|
||||
Host = host;
|
||||
HashedControlPassword = hashedControlPassword;
|
||||
Connect();
|
||||
}
|
||||
public void Connect()
|
||||
{
|
||||
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
sock.Connect(Host);
|
||||
socketStream = new NetworkStream(sock);
|
||||
socketReader = new StreamReader(socketStream);
|
||||
socketWriter = new StreamWriter(socketStream);
|
||||
socketWriter.AutoFlush = true;
|
||||
|
||||
recvThread = new Thread(new ParameterizedThreadStart(receiveLoop));
|
||||
recvThread.IsBackground = true;
|
||||
recvThread.Name = "Tor Control Library Receive Loop";
|
||||
recvThread.Start();
|
||||
|
||||
Authenticate();
|
||||
}
|
||||
public void Close()
|
||||
{
|
||||
sock.Close();
|
||||
}
|
||||
protected void Authenticate()
|
||||
{
|
||||
CommandResponse resp = SendCommand(String.Format("AUTHENTICATE \"{0}\"",HashedControlPassword));
|
||||
if (resp.StatusCode != 250)
|
||||
throw new TorAuthenticationException() { ErrorDescription = "There was a problem authenticating with the Tor control port.", HashedPassword = _hashedControlPassword, ServerResponse = resp.Response, StatusCode = resp.StatusCode };
|
||||
}
|
||||
public List<CircuitStatusResponse> GetCircuitStatuses()
|
||||
{
|
||||
List<CircuitStatusResponse> circuitResponses = new List<CircuitStatusResponse>();
|
||||
CommandResponse resp = SendCommand("GETINFO circuit-status");
|
||||
StringReader sr = new StringReader(resp.Response);
|
||||
String line = sr.ReadLine();
|
||||
if (!line.Contains("circuit-status"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
if (line[3] == '+' || line.EndsWith("="))
|
||||
line = sr.ReadLine();
|
||||
while (line != null)
|
||||
{
|
||||
if (line.Equals(".") || line.Equals("250 OK"))
|
||||
break;
|
||||
if (line.Contains("circuit-status"))
|
||||
line = line.Substring(line.IndexOf('=') + 1);
|
||||
circuitResponses.Add(CircuitStatusResponse.Parse(line));
|
||||
line = sr.ReadLine();
|
||||
}
|
||||
return circuitResponses;
|
||||
}
|
||||
public List<StreamStatusResponse> GetStreamStatuses()
|
||||
{
|
||||
List<StreamStatusResponse> streamResponses = new List<StreamStatusResponse>();
|
||||
CommandResponse resp = SendCommand("GETINFO stream-status");
|
||||
StringReader sr = new StringReader(resp.Response);
|
||||
String line = sr.ReadLine();
|
||||
if (!line.Contains("stream-status"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
if (line[3] == '+' || line.EndsWith("="))
|
||||
line = sr.ReadLine();
|
||||
while (line != null)
|
||||
{
|
||||
if (line.Equals(".") || line.Equals("250 OK"))
|
||||
break;
|
||||
if (line.Contains("stream-status"))
|
||||
line = line.Substring(line.IndexOf('=') + 1);
|
||||
streamResponses.Add(StreamStatusResponse.Parse(line));
|
||||
line = sr.ReadLine();
|
||||
}
|
||||
return streamResponses;
|
||||
}
|
||||
public List<RouterStatusResponse> GetAllRouterStatusInfo()
|
||||
{
|
||||
List<RouterStatusResponse> routerResponses = new List<RouterStatusResponse>();
|
||||
CommandResponse resp = SendCommand("GETINFO ns/all");
|
||||
StringReader sr = new StringReader(resp.Response);
|
||||
String line = sr.ReadLine(), routerLine = String.Empty, flagLine = String.Empty;
|
||||
if (!line.Contains("ns/all"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
|
||||
while (line != null)
|
||||
{
|
||||
line = sr.ReadLine();
|
||||
if (line.Equals("."))
|
||||
break;
|
||||
switch (line.Substring(0, 1))
|
||||
{
|
||||
case "r":
|
||||
routerLine = line;
|
||||
continue;
|
||||
case "s":
|
||||
flagLine = line;
|
||||
routerResponses.Add(RouterStatusResponse.Parse(routerLine, flagLine));
|
||||
break;
|
||||
default:
|
||||
//I'm not currently parsing "w" or "p" lines. p doesn't describe a complete exit policy, and "w" doesn't describe all the available bandwidth metrics. That's retrieved via GETINFO desc
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
sr.Close();
|
||||
return routerResponses;
|
||||
}
|
||||
public RouterStatusResponse GetRouterStatusInfoByNickname(String nickname)
|
||||
{
|
||||
RouterStatusResponse response = null;
|
||||
CommandResponse resp = SendCommand(String.Format("GETINFO ns/name/{0}", nickname));
|
||||
StringReader sr = new StringReader(resp.Response);
|
||||
String line = sr.ReadLine(), routerLine = String.Empty, flagLine = String.Empty;
|
||||
if (!line.Contains("ns/name"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
|
||||
while (line != null)
|
||||
{
|
||||
line = sr.ReadLine();
|
||||
if (line == null || line.Equals("."))
|
||||
break;
|
||||
switch (line.Substring(0, 1))
|
||||
{
|
||||
case "r":
|
||||
routerLine = line;
|
||||
continue;
|
||||
case "s":
|
||||
flagLine = line;
|
||||
response = RouterStatusResponse.Parse(routerLine, flagLine);
|
||||
break;
|
||||
default:
|
||||
//I'm not currently parsing "w" or "p" lines. p doesn't describe a complete exit policy, and "w" doesn't describe all the available bandwidth metrics. That's retrieved via GETINFO desc
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
sr.Close();
|
||||
return response;
|
||||
}
|
||||
public RouterDescription GetRouterDescriptionByNickname(String nickname)
|
||||
{
|
||||
RouterDescription routerDescription = new RouterDescription(nickname);
|
||||
|
||||
CommandResponse resp = SendCommand(String.Format("GETINFO desc/name/{0}", nickname));
|
||||
if (resp.StatusCode != 250)
|
||||
throw TorException.Parse(resp.Response);
|
||||
using (StringReader reader = new StringReader(resp.Response))
|
||||
{
|
||||
string line;
|
||||
String policy = String.Empty;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (!line.Contains(' ')) continue;
|
||||
Int32 firstSpace = line.IndexOf(' ');
|
||||
string key = line.Substring(0, firstSpace).Trim();
|
||||
string value = line.Substring(firstSpace, line.Length - firstSpace).Trim();
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "uptime":
|
||||
routerDescription.Uptime = new TimeSpan(0, 0, Int32.Parse(value));
|
||||
break;
|
||||
case "bandwidth":
|
||||
String[] values = value.Split(' ');
|
||||
routerDescription.BandwidthAverage = Int32.Parse(values[0]);
|
||||
routerDescription.BandwidthBurst = Int32.Parse(values[1]);
|
||||
routerDescription.BandwidthObserved = Int32.Parse(values[2]);
|
||||
break;
|
||||
case "reject":
|
||||
case "accept":
|
||||
policy += line + Environment.NewLine;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
routerDescription.ExitPolicy = new ExitPolicy(policy);
|
||||
}
|
||||
return routerDescription;
|
||||
}
|
||||
public RouterStatusResponse GetRouterStatusInfoByID(String identity)
|
||||
{
|
||||
CommandResponse resp = SendCommand(String.Format("GETINFO ns/id/{0}", identity));
|
||||
StringReader sr = new StringReader(resp.Response);
|
||||
String line = sr.ReadLine();
|
||||
if (!line.Contains("ns/id"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
|
||||
line = sr.ReadLine();
|
||||
String flagLine = sr.ReadLine();
|
||||
RouterStatusResponse routerResponse = RouterStatusResponse.Parse(line, flagLine);
|
||||
sr.Close();
|
||||
return routerResponse;
|
||||
}
|
||||
public void SetConfigurationEntry(String key, String value)
|
||||
{
|
||||
SendCommand(String.Format("SETCONF {0}={1}",key,value));
|
||||
}
|
||||
/// <summary>
|
||||
/// Starts the process of creating a new circuit
|
||||
/// NOTE: The circuit is not usable until the BUILT event occurs
|
||||
/// </summary>
|
||||
/// <param name="circuit">The new circuit ID</param>
|
||||
/// <returns></returns>
|
||||
public Int32 CreateCircuit(String circuit)
|
||||
{
|
||||
CommandResponse resp = SendCommand(String.Format("EXTENDCIRCUIT 0 {0}", circuit));
|
||||
if (resp.StatusCode == 250)
|
||||
{
|
||||
string[] parts = resp.Response.Split(' ');
|
||||
Int32 circuitID;
|
||||
if(Int32.TryParse(parts[parts.Length - 1],out circuitID))
|
||||
return circuitID;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public void AttachStream(Int32 streamId, Int32 circuitId)
|
||||
{
|
||||
SendCommand(String.Format("ATTACHSTREAM {0} {1}", streamId, circuitId));
|
||||
}
|
||||
public void CloseCircuit(Int32 circuitID)
|
||||
{
|
||||
SendCommand(String.Format("CLOSECIRCUIT {0}", circuitID));
|
||||
}
|
||||
public void CloseStream(Int32 streamID)
|
||||
{
|
||||
SendCommand(String.Format("CLOSESTREAM {0} 1", streamID));
|
||||
}
|
||||
|
||||
public CommandResponse SendRawCommand(String command)
|
||||
{
|
||||
return SendCommand(command);
|
||||
}
|
||||
public void RequestEvents(List<String> events)
|
||||
{
|
||||
SendRawCommand(String.Format("SETEVENTS {0}", String.Join(" ", events)));
|
||||
}
|
||||
private CommandResponse SendCommand(String command)
|
||||
{
|
||||
CommandResponse resp;
|
||||
lock (requestLock)
|
||||
{
|
||||
socketWriter.WriteLine(command);
|
||||
resp = GetLastCommandResponse();
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
private CommandResponse GetLastCommandResponse()
|
||||
{
|
||||
if (responseEvent.WaitOne(Timeout))
|
||||
{
|
||||
CommandResponse resp = responseQueue.Dequeue();
|
||||
if (!resp.StatusCode.ToString().StartsWith("2"))
|
||||
throw TorException.Parse(resp.Response);
|
||||
else return resp;
|
||||
}
|
||||
else
|
||||
throw new TimeoutException();
|
||||
}
|
||||
private void receiveLoop(object param)
|
||||
{
|
||||
String line;
|
||||
while (sock.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
line = socketReader.ReadLine();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (e.InnerException is SocketException &&
|
||||
((SocketException)e.InnerException).SocketErrorCode == SocketError.ConnectionAborted)
|
||||
break;
|
||||
else
|
||||
throw new ApplicationException();
|
||||
}
|
||||
if (line == null)
|
||||
break;
|
||||
if (line.Equals("") || line.Length < 5)
|
||||
continue;
|
||||
switch (line.Substring(0, 3))
|
||||
{
|
||||
//Command response
|
||||
case "250":
|
||||
case "246":
|
||||
case "249":
|
||||
CommandResponse resp = new CommandResponse(Int32.Parse(line.Substring(0,3)),line);
|
||||
|
||||
while (!line.Equals("250 OK") && !line.StartsWith("250 "))
|
||||
{
|
||||
line = socketReader.ReadLine();
|
||||
resp.AppendResponse(line);
|
||||
}
|
||||
responseQueue.Enqueue(resp);
|
||||
responseEvent.Set();
|
||||
break;
|
||||
//server-generated events
|
||||
case "650":
|
||||
EventResponse eventResp = EventResponse.Parse(line);
|
||||
if(ServerEvent != null)
|
||||
foreach (Action<EventResponse> serverEventResponse in ServerEvent.GetInvocationList())
|
||||
{
|
||||
serverEventResponse.BeginInvoke(eventResp, null, null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (line.StartsWith("5"))
|
||||
{
|
||||
responseQueue.Enqueue(new ErrorResponse(Int32.Parse(line.Substring(0, 3)), line));
|
||||
responseEvent.Set();
|
||||
}
|
||||
else
|
||||
throw new ApplicationException();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IPEndPoint Host { get; set; }
|
||||
public TimeSpan Timeout { get; set; }
|
||||
public event Action<EventResponse> ServerEvent;
|
||||
public Boolean Connected
|
||||
{
|
||||
get
|
||||
{
|
||||
return sock.Connected;
|
||||
}
|
||||
}
|
||||
public String HashedControlPassword
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hashedControlPassword;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!String.IsNullOrEmpty(value) && !value.StartsWith("16:"))
|
||||
throw new TorException() { ErrorDescription = "You must pass the already hashed version of the tor control port's password. You can obtain this by running the following command: tor.exe --hash-password mypass" };
|
||||
_hashedControlPassword = value;
|
||||
}
|
||||
}
|
||||
private String _hashedControlPassword;
|
||||
private StreamReader socketReader;
|
||||
private StreamWriter socketWriter;
|
||||
private NetworkStream socketStream;
|
||||
private Socket sock;
|
||||
private Thread recvThread;
|
||||
private readonly object requestLock = new object();
|
||||
private AutoResetEvent responseEvent = new AutoResetEvent(false);
|
||||
private Queue<CommandResponse> responseQueue = new Queue<CommandResponse>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user