/* This file is part of libtorcontrol.
*
* Copyright (C) 2010 Philipp Zabel
*
* libtorcontrol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libtorcontrol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libtorcontrol. If not, see .
*/
namespace TorControl {
// There are no Vala bindings for getaddrinfo, open socket in C code.
[CCode (cname = "tor_control_open_socket")]
extern int open_socket (int port) throws GLib.Error;
errordomain TorError {
UNRECOGNIZED_COMMAND_ARGUMENT = 513,
AUTHENTICATION_REQUIRED = 514,
BAD_AUTHENTICATION = 515,
UNSPECIFIED = 550,
UNRECOGNIZED_EVENT = 552
}
public class Connection : GLib.Object {
int control_port;
int socket_fd;
GLib.IOChannel channel;
bool async_lock = false;
public Connection () throws GLib.Error {
control_port = 9051;
socket_fd = TorControl.open_socket (control_port);
channel = new GLib.IOChannel.unix_new (socket_fd);
channel.set_encoding (null);
channel.set_buffered (false);
}
public Connection.with_port (int port) throws GLib.Error {
control_port = port;
socket_fd = TorControl.open_socket (control_port);
channel = new GLib.IOChannel.unix_new (socket_fd);
channel.set_encoding (null);
channel.set_buffered (false);
}
private void send_command (string command) throws GLib.ConvertError, GLib.IOChannelError {
size_t bytes_written;
unowned char[] buf = (char[]) command;
buf.length = (int) command.length;
channel.write_chars (buf, out bytes_written);
}
private string receive_result () throws GLib.ConvertError, GLib.IOChannelError, TorError {
size_t bytes_read;
char[] buf = new char[4096];
channel.read_chars (buf, out bytes_read);
buf[bytes_read] = 0;
// FIXME
unowned string p = (string) buf;
while (p.has_prefix ("6")) {
char *crlf = p.str ("\r\n");
if (crlf == null) {
print ("ERROR - missing newline\n");
return "";
}
crlf[0] = '\0';
print ("ASYNC: \"%s\"\n", p);
p = (string) (crlf + 2);
}
if (p.has_prefix ("2")) {
return "%s".printf (p);
} if (p.has_prefix ("5")) {
int code = p.to_int ();
string message = "%s".printf (p.offset (4));
switch (code) {
case 513:
throw new TorError.UNRECOGNIZED_COMMAND_ARGUMENT (message);
case 514:
throw new TorError.AUTHENTICATION_REQUIRED (message);
case 515:
throw new TorError.BAD_AUTHENTICATION (message);
case 550:
throw new TorError.UNSPECIFIED (message);
case 552:
throw new TorError.UNRECOGNIZED_EVENT (message);
default:
print ("Unknown error %d: \"%s\"\n", code, message);
return "";
}
} else {
print ("Error: \"%s\"\n", p);
return "";
}
}
private SourceFunc continuation;
private async string receive_result_async () throws GLib.ConvertError, GLib.IOChannelError, TorError {
channel.add_watch (IOCondition.IN | IOCondition.PRI, tor_io_func);
continuation = receive_result_async.callback;
yield;
string result = receive_result ();
async_lock = false;
return result;
}
private bool tor_io_func (IOChannel source, IOCondition condition) {
if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
if (async_lock)
continuation ();
}
return false;
}
public void authenticate (string password = "") throws GLib.ConvertError, GLib.IOChannelError, TorError {
send_command ("AUTHENTICATE \"%s\"\r\n".printf (password));
receive_result ();
}
public async void authenticate_async (string password = "") throws GLib.ConvertError, GLib.IOChannelError, TorError {
if (async_lock) {
throw new TorError.UNSPECIFIED ("only one async command at a time!");
}
async_lock = true;
send_command ("AUTHENTICATE \"%s\"\r\n".printf (password));
yield receive_result_async ();
}
public string get_conf (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
send_command ("GETCONF " + conf + "\r\n");
string result = receive_result ();
if (result[3] == '-') {
return result;
} if (result.offset (4).has_prefix (conf + "=")) {
return "%s".printf (result.offset (4 + conf.length + 1));
} else {
print ("get_conf error: \"%s\"\n", result);
return "";
}
}
public async string get_conf_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
if (async_lock) {
throw new TorError.UNSPECIFIED ("only one async command at a time!");
}
async_lock = true;
send_command ("GETCONF " + conf + "\r\n");
string result = yield receive_result_async ();
if (result[3] == '-') {
return result;
} if (result.offset (4).has_prefix (conf + "=")) {
return "%s".printf (result.offset (4 + conf.length + 1));
} else {
print ("get_conf error: \"%s\"\n", result);
return "";
}
}
public bool get_conf_bool (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
return (bool) get_conf (conf).to_int ();
}
public async bool get_conf_bool_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
var result = yield get_conf_async (conf).to_int ();
return (bool) result;
}
public SList get_conf_list (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
string result = get_conf (conf);
return evaluate_list (conf, result);
}
public async SList get_conf_list_async (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
string result = yield get_conf_async (conf);
return evaluate_list (conf, result);
}
private SList evaluate_list (string conf, string result) {
string prefix = conf + "=";
SList list = new SList ();
unowned string p = result;
for (char *crlf = p.str ("\r\n"); crlf != null; crlf = p.str ("\r\n")) {
crlf[0] = '\0';
if (p.offset (4).has_prefix (prefix)) {
list.append (p.offset (4 + prefix.length));
}
p = (string) (crlf + 2);
}
return list;
}
public void set_conf (string conf, string val) throws GLib.ConvertError, GLib.IOChannelError, TorError {
send_command ("SETCONF " + conf + "=" + val + "\r\n");
string result = receive_result ();
if (!result.offset (4).has_prefix ("OK")) {
print ("set_conf error: \"%s\"\n", result);
}
}
public void set_conf_bool (string conf, bool val) throws GLib.ConvertError, GLib.IOChannelError, TorError {
set_conf (conf, val ? "1" : "0");
}
public void set_conf_list (string conf, SList values) throws GLib.ConvertError, GLib.IOChannelError, TorError {
string command = "SETCONF";
foreach (string val in values) {
command += " %s=%s".printf (conf, val);
}
send_command (command + "\r\n");
string result = receive_result ();
if (!result.offset (4).has_prefix ("OK")) {
print ("set_conf_list error: \"%s\"\n", result);
}
}
public void set_events (string events) throws GLib.ConvertError, GLib.IOChannelError, TorError {
send_command ("SETEVENTS " + events + "\r\n");
receive_result ();
}
}
}