305 lines
9.5 KiB
C#
305 lines
9.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Net.Sockets;
|
|
using System.IO;
|
|
|
|
namespace Tor.Controller
|
|
{
|
|
/// <summary>
|
|
/// A class containing methods for interacting with a control connection for a tor application.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Connection"/> class.
|
|
/// </summary>
|
|
/// <param name="client">The client hosting the control connection.</param>
|
|
public Connection(Client client)
|
|
{
|
|
this.client = client;
|
|
this.disposed = false;
|
|
this.reader = null;
|
|
this.socket = null;
|
|
this.stream = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizes an instance of the <see cref="Connection"/> class.
|
|
/// </summary>
|
|
~Connection()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#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;
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Authenticates the connection by sending the password to the control port.
|
|
/// </summary>
|
|
/// <param name="password">The password used for authentication.</param>
|
|
/// <returns><c>true</c> if the authentication succeeds; otherwise, <c>false</c>.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connects to the control port hosted by the client.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if the connection succeeds; otherwise, <c>false</c>.</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a response buffer from the control connection. This method is blocking with a receive timeout of 500ms.
|
|
/// </summary>
|
|
/// <returns>A <see cref="ConnectionResponse"/> containing the response information.</returns>
|
|
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<string>() { "" });
|
|
|
|
if (line[0] != '+' && line[0] != '-')
|
|
{
|
|
if (line[0] == ' ')
|
|
line = line.Substring(1);
|
|
|
|
return new ConnectionResponse((StatusCode)code, new List<string>() { line });
|
|
}
|
|
|
|
char id = line[0];
|
|
|
|
List<string> responses = new List<string>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a command to the connection and flushes the buffer to the control port.
|
|
/// </summary>
|
|
/// <param name="command">The command to write to the connection.</param>
|
|
/// <returns><c>true</c> if the command is dispatched successfully; otherwise, <c>false</c>.</returns>
|
|
public bool Write(string command)
|
|
{
|
|
if (command == null)
|
|
throw new ArgumentNullException("command");
|
|
|
|
if (!command.EndsWith(EOL))
|
|
command += EOL;
|
|
|
|
return Write(Encoding.ASCII.GetBytes(command));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a command to the connection and flushes the buffer to the control port.
|
|
/// </summary>
|
|
/// <param name="command">The command to write to the connection.</param>
|
|
/// <param name="parameters">An optional collection of parameters to serialize into the command.</param>
|
|
/// <returns><c>true</c> if the command is dispatched successfully; otherwise, <c>false</c>.</returns>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a command to the connection and flushes the buffer to the control port.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer containing the command data.</param>
|
|
/// <returns><c>true</c> if the command is dispatched successfully; otherwise, <c>false</c>.</returns>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|