Add TorControl
authorPhilipp Zabel <philipp.zabel@gmail.com>
Fri, 26 Feb 2010 17:19:01 +0000 (18:19 +0100)
committerPhilipp Zabel <philipp.zabel@gmail.com>
Sun, 28 Feb 2010 18:33:56 +0000 (19:33 +0100)
Makefile.am
configure.ac
src/torcontrol-socket.c [new file with mode: 0644]
src/torcontrol.vala [new file with mode: 0644]

index a0aaec9..e7d7778 100644 (file)
@@ -22,10 +22,13 @@ icon18_DATA = \
        data/statusarea_tor_connecting.png
 
 status_area_applet_tor_la_SOURCES = \
-       src/status-area-applet-tor.c
+       src/status-area-applet-tor.c \
+       src/torcontrol.c \
+       src/torcontrol-socket.c
 
 status_area_applet_tor_la_VALASOURCES = \
-       src/status-area-applet-tor.vala
+       src/status-area-applet-tor.vala \
+       src/torcontrol.vala
 
 src/status-area-applet-tor.c: ${status_area_applet_tor_la_VALASOURCES}
        ${VALAC} -C ${status_area_applet_tor_la_VALAFLAGS} $^
index d934f83..3441f00 100644 (file)
@@ -14,7 +14,7 @@ AC_STDC_HEADERS
 AC_PROG_INSTALL
 AC_PROG_LIBTOOL
 
-CFLAGS="$CFLAGS -Wall -ansi -Wmissing-prototypes -Wmissing-declarations"
+CFLAGS="$CFLAGS -Wall -Wmissing-prototypes -Wmissing-declarations"
 
 PKG_CHECK_MODULES(CONIC, conic >= 0.22)
 AC_SUBST(CONIC_LIBS)
diff --git a/src/torcontrol-socket.c b/src/torcontrol-socket.c
new file mode 100644 (file)
index 0000000..a8002d0
--- /dev/null
@@ -0,0 +1,80 @@
+/* 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;
+}
+
diff --git a/src/torcontrol.vala b/src/torcontrol.vala
new file mode 100644 (file)
index 0000000..817255b
--- /dev/null
@@ -0,0 +1,248 @@
+/* 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 ();
+               }
+
+       }
+}