--- /dev/null
+/* This file is part of status-area-applet-tor.
+ *
+ * Copyright (C) 2010 Philipp Zabel
+ *
+ * status-area-applet-tor 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.
+ *
+ * status-area-applet-tor 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 status-area-applet-tor. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+[Compact]
+class ProxyBackup {
+ public bool use_http_proxy;
+ public string http_host;
+ public string socks_host;
+ public string secure_host;
+ public int http_port;
+ public int socks_port;
+ public int secure_port;
+ public string mode;
+}
+
+class TorStatusMenuItem : HD.StatusMenuItem {
+ private const string STATUSMENU_TOR_LIBOSSO_SERVICE_NAME = "tor_status_menu_item";
+
+ private const int STATUS_MENU_ICON_SIZE = 48;
+ private const int STATUS_AREA_ICON_SIZE = 18;
+
+ private const string GCONF_DIR_TOR = "/apps/maemo/tor";
+ private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled";
+
+ private const string GCONF_DIR_PROXY_HTTP = "/system/http_proxy";
+ private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
+ private const string GCONF_KEY_PROXY_HTTP_HOST = GCONF_DIR_PROXY_HTTP + "/host";
+ private const string GCONF_KEY_PROXY_HTTP_PORT = GCONF_DIR_PROXY_HTTP + "/port";
+
+ private const string GCONF_DIR_PROXY = "/system/proxy";
+ private const string GCONF_KEY_PROXY_MODE = GCONF_DIR_PROXY + "/mode";
+ private const string GCONF_KEY_PROXY_SOCKS_HOST = GCONF_DIR_PROXY + "/socks_host";
+ private const string GCONF_KEY_PROXY_SOCKS_PORT = GCONF_DIR_PROXY + "/socks_port";
+ private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
+ private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
+
+ // Widgets
+ Hildon.Button button;
+
+ // Icons
+ Gdk.Pixbuf icon_disabled;
+ Gdk.Pixbuf icon_connecting;
+ Gdk.Pixbuf icon_connected;
+
+ // ConIc, GConf and Osso context
+ Osso.Context osso;
+ GConf.Client gconf;
+ ConIc.Connection conic;
+ bool conic_connected;
+
+ // Internal state
+ bool tor_enabled;
+ bool tor_connected;
+ Pid tor_pid;
+ int tor_stdout;
+ Pid polipo_pid;
+ ProxyBackup backup;
+
+ /**
+ * Update status area icon and status menu button value
+ */
+ private void update_status () {
+ Gtk.IconTheme icon_theme;
+ Gdk.Pixbuf pixbuf;
+
+ if (tor_enabled && tor_connected && icon_connected == null) try {
+ icon_theme = Gtk.IconTheme.get_default ();
+ pixbuf = icon_theme.load_icon ("tor_status_connected",
+ STATUS_AREA_ICON_SIZE,
+ Gtk.IconLookupFlags.NO_SVG);
+ icon_connected = pixbuf;
+ } catch (Error e) {
+ error (e.message);
+ }
+ if (!tor_enabled && icon_disabled == null) try {
+ icon_theme = Gtk.IconTheme.get_default ();
+ pixbuf = icon_theme.load_icon ("tor_status_disabled",
+ STATUS_AREA_ICON_SIZE,
+ Gtk.IconLookupFlags.NO_SVG);
+ icon_disabled = pixbuf;
+ } catch (Error e) {
+ error (e.message);
+ }
+ if (tor_enabled && !tor_connected && icon_connecting == null) try {
+ icon_theme = Gtk.IconTheme.get_default ();
+ pixbuf = icon_theme.load_icon ("tor_status_connecting",
+ STATUS_AREA_ICON_SIZE,
+ Gtk.IconLookupFlags.NO_SVG);
+ icon_connecting = pixbuf;
+ } catch (Error e) {
+ error (e.message);
+ }
+
+ if (conic_connected && tor_enabled) {
+ pixbuf = tor_connected ? icon_connected : icon_connecting;
+ button.set_value (tor_connected ? "Connected" : "Connecting ...");
+ } else {
+ pixbuf = conic_connected ? icon_disabled : null;
+ button.set_value (tor_enabled ? "Disconnected" : "Disabled");
+ }
+ set_status_area_icon (pixbuf);
+ }
+
+ /**
+ * Callback for Tor daemon line output
+ */
+ private bool tor_io_func (IOChannel source, IOCondition condition) {
+
+ if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
+ string line = null;
+ size_t length;
+ try {
+ /* var status = */ source.read_line (out line, out length, null);
+
+ if ("[notice]" in line) {
+ if ("Bootstrapped 100%" in line) {
+ tor_connected = true;
+ proxy_setup ();
+ update_status ();
+ }
+ } else {
+ // FIXME
+ Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
+ }
+ } catch (Error e) {
+ // FIXME
+ Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
+ }
+ }
+ if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Start Tor and setup proxy settings
+ */
+ private void start_tor () {
+ try {
+ if (tor_pid == (Pid) 0) {
+ Process.spawn_async_with_pipes ("/tmp",
+ { "/usr/sbin/tor" },
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out tor_pid,
+ null,
+ out tor_stdout);
+
+ var channel = new IOChannel.unix_new (tor_stdout);
+ channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
+ }
+ if (polipo_pid == (Pid) 0) {
+ Process.spawn_async_with_pipes ("/tmp",
+ { "/usr/bin/polipo" },
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out polipo_pid);
+ }
+
+ /* --> proxy settings and will be set up and tor_connected will
+ * be set to true once Tor signals 100%
+ */
+ } catch (SpawnError e) {
+ error ("Failed to spawn polipo and tor: %s", e.message);
+ return;
+ }
+
+ update_status ();
+ }
+
+ /**
+ * Stop Tor and revert proxy settings
+ */
+ private void stop_tor () {
+ proxy_restore ();
+ tor_connected = false;
+ if (polipo_pid != (Pid) 0) {
+ Process.close_pid (polipo_pid);
+ Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
+ polipo_pid = (Pid) 0;
+ }
+ if (tor_pid != (Pid) 0) {
+ Process.close_pid (tor_pid);
+ Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
+ tor_pid = (Pid) 0;
+ }
+
+ update_status ();
+ }
+
+ /**
+ * Setup proxy settings to route through the Tor network
+ */
+ private void proxy_setup () {
+ if (backup == null) try {
+ backup = new ProxyBackup ();
+ backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
+
+ backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
+ backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
+ backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
+ backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
+ backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
+ backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
+
+ backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
+ } catch (Error e) {
+ error ("Error saving proxy settings: %s", e.message);
+ backup = new ProxyBackup ();
+ backup.use_http_proxy = false;
+
+ backup.http_host = "";
+ backup.socks_host = "";
+ backup.secure_host = "";
+ backup.http_port = 8080;
+ backup.socks_port = 0;
+ backup.secure_port = 0;
+
+ backup.mode = "none";
+ }
+ try {
+ // Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
+ gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
+
+ gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
+ gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
+ gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
+ gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
+ gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
+ gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
+
+ gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
+ } catch (Error e) {
+ error ("Error changing proxy settings: %s", e.message);
+ }
+ }
+
+ /**
+ * Revert proxy settings
+ */
+ private void proxy_restore () {
+ if (backup != null) try {
+ // Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
+ gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
+
+ gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
+ gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
+ gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
+ gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
+ gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
+ gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
+
+ gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
+ backup = null;
+ } catch (Error e) {
+ error ("Error restoring proxy: %s", e.message);
+ }
+ }
+
+ /**
+ * Callback for the status menu button clicked signal
+ */
+ private void button_clicked_cb () {
+ var dialog = new Gtk.Dialog ();
+ var content = (Gtk.VBox) dialog.get_content_area ();
+
+ dialog.set_title ("The Onion Router");
+
+ var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
+ check.set_label ("Enable onion routing");
+ check.set_active (tor_enabled);
+ content.pack_start (check, true, true, 0);
+
+ dialog.add_button ("Save", Gtk.ResponseType.ACCEPT);
+ dialog.response.connect ((response_id) => {
+ if (response_id == Gtk.ResponseType.ACCEPT) {
+ if (!tor_enabled && check.get_active ()) {
+ tor_enabled = true;
+
+ if (conic_connected) {
+ start_tor ();
+ } else {
+ conic.connect (ConIc.ConnectFlags.NONE);
+ }
+ } else if (tor_enabled && !check.get_active ()) {
+ tor_enabled = false;
+
+ stop_tor ();
+ if (conic_connected)
+ conic.disconnect ();
+ }
+ }
+ dialog.destroy ();
+ });
+
+ dialog.show_all ();
+ }
+
+ /**
+ * Callback for the ConIc connection-event signal
+ */
+ private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
+ var status = event.get_status ();
+ switch (status) {
+ case ConIc.ConnectionStatus.CONNECTED:
+ conic_connected = true;
+ if (tor_enabled) {
+ start_tor ();
+ } else {
+ update_status ();
+ }
+ break;
+ case ConIc.ConnectionStatus.DISCONNECTING:
+ conic_connected = false;
+ stop_tor ();
+ break;
+ case ConIc.ConnectionStatus.DISCONNECTED:
+ case ConIc.ConnectionStatus.NETWORK_UP:
+ // ignore
+ break;
+ }
+
+ var error = event.get_error ();
+ switch (error) {
+ case ConIc.ConnectionError.CONNECTION_FAILED:
+ Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
+ break;
+ case ConIc.ConnectionError.USER_CANCELED:
+ Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
+ break;
+ case ConIc.ConnectionError.NONE:
+ case ConIc.ConnectionError.INVALID_IAP:
+ // ignore
+ break;
+ }
+ }
+
+ private void create_widgets () {
+ Gtk.IconTheme icon_theme;
+ Gdk.Pixbuf pixbuf;
+ Gtk.Image image;
+
+ // Status menu button
+ button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
+ Hildon.ButtonArrangement.VERTICAL,
+ "The Onion Router",
+ tor_enabled ? "Enabled" : "Disabled");
+ icon_theme = Gtk.IconTheme.get_default();
+ try {
+ pixbuf = icon_theme.load_icon ("tor_onion",
+ STATUS_MENU_ICON_SIZE,
+ Gtk.IconLookupFlags.NO_SVG);
+ image = new Gtk.Image.from_pixbuf (pixbuf);
+ button.set_image (image);
+ } catch (Error e) {
+ error (e.message);
+ }
+ button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
+ button.clicked.connect (button_clicked_cb);
+
+ add (button);
+
+ // Status area icon
+ update_status ();
+
+ show_all ();
+ }
+
+ construct {
+ // GConf hook-up
+ gconf = GConf.Client.get_default ();
+ try {
+ tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
+ } catch (Error e) {
+ error ("Failed to get GConf setting: %s", e.message);
+ }
+ tor_connected = false;
+
+ // ConIc hook-up
+ conic = new ConIc.Connection ();
+ if (conic == null) {
+ Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
+ }
+ conic_connected = false;
+ conic.automatic_connection_events = true;
+ if (tor_enabled)
+ conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
+ conic.connection_event.connect (conic_connection_event_cb);
+
+ // Osso hook-up
+ osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
+ "0.0.1",
+ true,
+ null);
+
+ create_widgets ();
+ }
+}
+
+/**
+ * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
+ * most of the class registration issues itself. Only this code from
+ * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
+ * to register with hildon-desktop:
+ */
+[ModuleInit]
+public void hd_plugin_module_load (TypeModule plugin) {
+ // [ModuleInit] registers types automatically
+ ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
+}
+
+public void hd_plugin_module_unload (HD.PluginModule plugin) {
+}