/* 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 . */ [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_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 ("statusarea_tor_connected", STATUS_AREA_ICON_SIZE, Gtk.IconLookupFlags.NO_SVG); icon_connected = 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 ("statusarea_tor_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 = 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 (_("Tor: anonymity online")); 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.set_style (Hildon.ButtonStyle.PICKER); button.clicked.connect (button_clicked_cb); add (button); // Status area icon update_status (); show_all (); } construct { // Gettext hook-up Intl.setlocale (LocaleCategory.ALL, ""); Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); Intl.textdomain (Config.GETTEXT_PACKAGE); // 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, Config.VERSION, 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) { }