X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fstatus-area-applet-tor.vala;h=dc7e35ef534f49eaff66bd97d69cdde2c889618b;hb=164dbb8b6045ba1664a08ccd45b1e68570a3efa7;hp=76472390e483eb6f9cd1fc3e46f0644ce9505c6e;hpb=d99c1521abadfd6ce0e95047c1b32430ba3f5915;p=tor-status diff --git a/src/status-area-applet-tor.vala b/src/status-area-applet-tor.vala index 7647239..dc7e35e 100644 --- a/src/status-area-applet-tor.vala +++ b/src/status-area-applet-tor.vala @@ -36,6 +36,7 @@ class TorStatusMenuItem : HD.StatusMenuItem { private const string GCONF_DIR_TOR = "/apps/maemo/tor"; private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled"; + private const string GCONF_KEY_BRIDGES = GCONF_DIR_TOR + "/bridges"; 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"; @@ -53,9 +54,10 @@ class TorStatusMenuItem : HD.StatusMenuItem { Hildon.Button button; // Icons - Gdk.Pixbuf icon_disabled; Gdk.Pixbuf icon_connecting; Gdk.Pixbuf icon_connected; + Gtk.Image icon_enabled; + Gtk.Image icon_disabled; // ConIc, GConf and Osso context Osso.Context osso; @@ -70,50 +72,59 @@ class TorStatusMenuItem : HD.StatusMenuItem { int tor_stdout; Pid polipo_pid; ProxyBackup backup; + string tor_log; + TorControl.Connection tor_control; + string password; /** * 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); + var icon_theme = Gtk.IconTheme.get_default (); + var 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 && 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; + if (tor_enabled && !tor_connected && icon_connecting == null) try { + var icon_theme = Gtk.IconTheme.get_default (); + var 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 (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; + if (tor_enabled && icon_enabled == null) try { + var icon_theme = Gtk.IconTheme.get_default(); + var pixbuf = icon_theme.load_icon ("statusarea_tor_enabled", + STATUS_MENU_ICON_SIZE, + Gtk.IconLookupFlags.NO_SVG); + icon_enabled = new Gtk.Image.from_pixbuf (pixbuf); + } catch (Error e) { + error (e.message); + } + if (!tor_enabled && icon_disabled == null) try { + var icon_theme = Gtk.IconTheme.get_default(); + var pixbuf = icon_theme.load_icon ("statusarea_tor_disabled", + STATUS_MENU_ICON_SIZE, + Gtk.IconLookupFlags.NO_SVG); + icon_disabled = new Gtk.Image.from_pixbuf (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 ..."); + set_status_area_icon (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 (null); + button.set_value (tor_enabled ? _("Disconnected") : _("Disabled")); } - set_status_area_icon (pixbuf); + button.set_image (tor_enabled ? icon_enabled : icon_disabled); } /** @@ -127,12 +138,17 @@ class TorStatusMenuItem : HD.StatusMenuItem { try { /* var status = */ source.read_line (out line, out length, null); + tor_log += line; if ("[notice]" in line) { if ("Bootstrapped 100%" in line) { tor_connected = true; proxy_setup (); update_status (); } + if ("Opening Control listener on 127.0.0.1:9051" in line) { + tor_control = new TorControl.Connection (); + tor_control_auth.begin (); + } } else { // FIXME Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line)); @@ -149,13 +165,67 @@ class TorStatusMenuItem : HD.StatusMenuItem { } /** + * Authenticate with Tor on the control channel + */ + private async void tor_control_auth () throws Error { + yield tor_control.authenticate_async (password); + + var bridges = new SList (); + try { + bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); + } catch (Error e) { + error ("Error loading bridges: %s", e.message); + return; + } + + if (bridges.length () <= 0) + return; + + // Enable bridge relays + tor_control.set_conf_list ("Bridge", bridges); + tor_control.set_conf_bool ("UseBridges", true); + + bool use = yield tor_control.get_conf_bool_async ("UseBridges"); + if (!use) { + Hildon.Banner.show_information (null, null, + "Failed to set up bridge relays"); + } + } + + /** * Start Tor and setup proxy settings */ private void start_tor () { try { if (tor_pid == (Pid) 0) { + string[] tor_hash_argv = { + "/usr/sbin/tor", + "--hash-password", "", + null + }; + var tv = TimeVal (); + Random.set_seed ((uint32) tv.tv_usec); + password = "tor-status-%8x".printf (Random.next_int ()); + tor_hash_argv[2] = password; + string hash; + Process.spawn_sync ("/tmp", tor_hash_argv, null, 0, null, out hash); + hash = hash.str ("16:").replace ("\n", ""); + + if (hash == null) { + Hildon.Banner.show_information (null, null, + "Failed to get hash"); + return; + } + + string[] tor_argv = { + "/usr/sbin/tor", + "--ControlPort", "9051", + "--HashedControlPassword", "", + null + }; + tor_argv[4] = hash; Process.spawn_async_with_pipes ("/tmp", - { "/usr/sbin/tor" }, + tor_argv, null, SpawnFlags.SEARCH_PATH, null, @@ -179,10 +249,11 @@ class TorStatusMenuItem : HD.StatusMenuItem { * be set to true once Tor signals 100% */ } catch (SpawnError e) { - error ("Failed to spawn polipo and tor: %s", e.message); + Hildon.Banner.show_information (null, null, "DEBUG: Failed to spawn polipo and tor: %s".printf (e.message)); return; } + tor_log = ""; update_status (); } @@ -276,21 +347,254 @@ class TorStatusMenuItem : HD.StatusMenuItem { } /** + * Show the bridge relay configuration dialog + */ + private const int RESPONSE_NEW = 1; + private void bridges_clicked_cb () { + var dialog = new Gtk.Dialog (); + var content = (Gtk.VBox) dialog.get_content_area (); + content.set_size_request (-1, 5*70); + + dialog.set_title (_("Bridge relays")); + + var bridges = new SList (); + try { + bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); + } catch (Error e) { + Hildon.Banner.show_information (null, null, "Error loading bridges: %s".printf (e.message)); + } + + var list_store = new Gtk.ListStore (1, typeof (string)); + Gtk.TreeIter iter; + foreach (string bridge in bridges) { + list_store.append (out iter); + list_store.@set (iter, 0, bridge); + } + + var pannable_area = new Hildon.PannableArea (); + var tree_view = new Gtk.TreeView.with_model (list_store); + var renderer = new Gtk.CellRendererText (); + var column = new Gtk.TreeViewColumn.with_attributes ("IP", renderer, "text", 0); + tree_view.append_column (column); + pannable_area.add (tree_view); + content.pack_start (pannable_area, true, true, 0); + + tree_view.row_activated.connect ((path, column) => { + bridge_edit_dialog (list_store, path); + }); + + dialog.add_button (_("New"), RESPONSE_NEW); + dialog.response.connect ((response_id) => { + if (response_id == RESPONSE_NEW) { + bridge_edit_dialog (list_store, null); + } + }); + + dialog.show_all (); + } + + /** + * Show the bridge relay edit dialog + */ + private const int RESPONSE_DELETE = 1; + private void bridge_edit_dialog (Gtk.ListStore store, Gtk.TreePath? path) { + var dialog = new Gtk.Dialog (); + var content = (Gtk.VBox) dialog.get_content_area (); + + if (path == null) + dialog.set_title (_("New bridge relay")); + else + dialog.set_title (_("Edit bridge relay")); + + var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL); + + var hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE); + var label = new Gtk.Label (_("IP address")); + label.set_alignment (0, 0.5f); + size_group.add_widget (label); + hbox.pack_start (label, false, false, 0); + var ip_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT); + ip_entry.set ("hildon-input-mode", Hildon.GtkInputMode.NUMERIC | + Hildon.GtkInputMode.SPECIAL); + hbox.pack_start (ip_entry, true, true, 0); + content.pack_start (hbox, false, false, 0); + + hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE); + label = new Gtk.Label (_("Port")); + label.set_alignment (0, 0.5f); + size_group.add_widget (label); + hbox.pack_start (label, false, false, 0); + var port_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT); + port_entry.set ("hildon-input-mode", Hildon.GtkInputMode.NUMERIC); + hbox.pack_start (port_entry, true, true, 0); + content.pack_start (hbox, true, true, 0); + + hbox = new Gtk.HBox (false, Hildon.MARGIN_DOUBLE); + label = new Gtk.Label (_("Fingerprint")); + label.set_alignment (0, 0.5f); + size_group.add_widget (label); + hbox.pack_start (label, false, false, 0); + var fingerprint_entry = new Hildon.Entry (Hildon.SizeType.FINGER_HEIGHT); + fingerprint_entry.set ("hildon-input-mode", Hildon.GtkInputMode.HEXA); + hbox.pack_start (fingerprint_entry, true, true, 0); + content.pack_start (hbox, true, true, 0); + + var iter = Gtk.TreeIter (); + if (path == null) { + port_entry.set_text ("443"); + } else if (store.get_iter (out iter, path)) { + string tmp; + store.@get (iter, 0, out tmp); + string[] ip_port = tmp.split (":"); + if (ip_port.length == 2) { + ip_entry.set_text (ip_port[0]); + port_entry.set_text (ip_port[1]); + } + + dialog.add_button (_("Delete"), RESPONSE_DELETE); + } + dialog.add_button (_("Save"), Gtk.ResponseType.OK); + dialog.response.connect ((response_id) => { + var bridges = new SList (); + + if (response_id == RESPONSE_DELETE) { + if (path != null) { + store.remove (iter); + string bridge; + if (store.get_iter_first (out iter)) do { + store.@get (iter, 0, out bridge); + bridges.append (bridge); + } while (store.iter_next (ref iter)); + try { + gconf.set_list (GCONF_KEY_BRIDGES, + GConf.ValueType.STRING, + bridges); + } catch (Error e) { + Hildon.Banner.show_information (dialog, null, + "Failed to save bridge relay list: %s".printf (e.message)); + } + } + dialog.destroy (); + } + if (response_id == Gtk.ResponseType.OK) { + if (!is_valid_ip_address (ip_entry.get_text ())) { + Hildon.Banner.show_information (dialog, null, + _("Invalid IP address")); + return; + } + int port = port_entry.get_text ().to_int (); + if (port < 0 || port > 65565) { + Hildon.Banner.show_information (dialog, null, + _("Invalid port number")); + return; + } + if (path == null) { + store.append (out iter); + } + store.@set (iter, 0, "%s:%d".printf (ip_entry.get_text (), port)); + try { + bridges = gconf.get_list (GCONF_KEY_BRIDGES, + GConf.ValueType.STRING); + } catch (Error e) { + Hildon.Banner.show_information (null, null, + "Error loading bridges: %s".printf (e.message)); + } + if (path == null) { + bridges.append ("%s:%d".printf (ip_entry.get_text (), port)); + } else { + bridges = null; + string bridge; + if (store.get_iter_first (out iter)) do { + store.@get (iter, 0, out bridge); + bridges.append (bridge); + } while (store.iter_next (ref iter)); + } + try { + gconf.set_list (GCONF_KEY_BRIDGES, + GConf.ValueType.STRING, + bridges); + } catch (Error e) { + Hildon.Banner.show_information (dialog, null, + "Failed to save bridge relay list: %s".printf (e.message)); + } + + dialog.destroy (); + } + }); + + dialog.show_all (); + } + + /** + * Check whether the IP address consists of four numbers in the 0..255 range + */ + bool is_valid_ip_address (string address) { + string[] ip = address.split ("."); + + if (ip.length != 4) + return false; + + for (int i = 0; i < ip.length; i++) { + int n = ip[i].to_int (); + if (n < 0 || n > 255) + return false; + } + + return true; + } + + /** + * Show the Tor log dialog + */ + private void show_tor_log () { + var dialog = new Gtk.Dialog (); + var content = (Gtk.VBox) dialog.get_content_area (); + content.set_size_request (-1, 5*70); + + dialog.set_title (_("Log")); + + var pannable = new Hildon.PannableArea (); + pannable.mov_mode = Hildon.MovementMode.BOTH; + var label = new Gtk.Label (tor_log); + pannable.add_with_viewport (label); + content.pack_start (pannable, true, true, 0); + + dialog.show_all (); + } + + /** * Callback for the status menu button clicked signal */ + private const int RESPONSE_LOG = 1; private void button_clicked_cb () { var dialog = new Gtk.Dialog (); var content = (Gtk.VBox) dialog.get_content_area (); + content.set_size_request (-1, 2*70); - dialog.set_title ("The Onion Router"); + dialog.set_title (_("Tor: anonymity online")); var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT); - check.set_label ("Enable onion routing"); + 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); + var button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT, + Hildon.ButtonArrangement.VERTICAL, + _("Bridge relays"), + get_bridge_list ()); + button.set_style (Hildon.ButtonStyle.PICKER); + button.set_alignment (0, 0.5f, 0, 0.5f); + button.clicked.connect (bridges_clicked_cb); + content.pack_start (button, true, true, 0); + + dialog.add_button (_("Log"), RESPONSE_LOG); + + dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT); dialog.response.connect ((response_id) => { + if (response_id == RESPONSE_LOG) { + show_tor_log (); + return; + } if (response_id == Gtk.ResponseType.ACCEPT) { if (!tor_enabled && check.get_active ()) { tor_enabled = true; @@ -314,6 +618,26 @@ class TorStatusMenuItem : HD.StatusMenuItem { dialog.show_all (); } + private string get_bridge_list () { + string list = null; + var bridges = new SList (); + try { + bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); + } catch (Error e) { + error ("Error loading bridges: %s", e.message); + } + foreach (string bridge in bridges) { + if (list == null) + list = bridge; + else + list += ", " + bridge; + } + if (list == null) + list = _("None"); + + return list; + } + /** * Callback for the ConIc connection-event signal */ @@ -354,26 +678,13 @@ class TorStatusMenuItem : HD.StatusMenuItem { } 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); - } + _("The Onion Router"), + tor_enabled ? _("Enabled") : _("Disabled")); 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); @@ -385,6 +696,11 @@ class TorStatusMenuItem : HD.StatusMenuItem { } 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 {