/* 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 (); } } }