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;
}
}
}
}