--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <glib.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+int tor_control_open_socket (int port, GError **error);
+
+int tor_control_open_socket (int port, GError **error) {
+ int status;
+ char *service;
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sockfd;
+ int fd;
+
+ memset (&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ service = g_strdup_printf ("%d", port);
+
+ status = getaddrinfo (NULL, service, &hints, &result);
+
+ g_free (service);
+
+ if (status != 0) {
+ g_set_error (error,
+ /* domain: */ g_quark_from_string ("TORCTLERR"), /* code: */ 1,
+ "getaddrinfo error: %s", gai_strerror (status));
+ return -1;
+ }
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sockfd = socket (result->ai_family, result->ai_socktype,
+ result->ai_protocol);
+ if (sockfd == -1)
+ continue;
+
+ status = connect (sockfd, result->ai_addr, result->ai_addrlen);
+ if (status != -1)
+ break;
+
+ close (sockfd);
+ }
+
+ freeaddrinfo (result);
+
+ if (rp == NULL) {
+ g_set_error (error,
+ /* domain: */ g_quark_from_string ("TORCTLERR"), /* code: */ 2,
+ "socket/connect error: %s", strerror (errno));
+ return -1;
+ }
+
+ return sockfd;
+}
+
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+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<string> get_conf_list (string conf) throws GLib.ConvertError, GLib.IOChannelError, TorError {
+ string result = get_conf (conf);
+ return evaluate_list (conf, result);
+ }
+
+ public async SList<string> 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<string> evaluate_list (string conf, string result) {
+ string prefix = conf + "=";
+ SList<string> list = new SList<string> ();
+ 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<string> 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 ();
+ }
+
+ }
+}