X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fstatus-area-applet-tor.vala;h=285c99c00a85185bce24b8b0e3186587a10812ab;hb=HEAD;hp=fc39badf892640eac038c4f1f73ddd1b39e29fb7;hpb=e9fb2b739f7a375574bedb51d8cdb9df4e44a8fa;p=tor-status diff --git a/src/status-area-applet-tor.vala b/src/status-area-applet-tor.vala index fc39bad..285c99c 100644 --- a/src/status-area-applet-tor.vala +++ b/src/status-area-applet-tor.vala @@ -1,6 +1,6 @@ /* This file is part of status-area-applet-tor. * - * Copyright (C) 2010 Philipp Zabel + * Copyright (C) 2010-2011 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 @@ -37,6 +37,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_KEY_EXITNODES = GCONF_DIR_TOR + "/exit_nodes"; 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"; @@ -52,6 +53,7 @@ class TorStatusMenuItem : HD.StatusMenuItem { // Widgets Hildon.Button button; + Gtk.Label log_label; // Icons Gdk.Pixbuf icon_connecting; @@ -73,46 +75,48 @@ class TorStatusMenuItem : HD.StatusMenuItem { 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 () { - if (tor_enabled && tor_connected && icon_connected == null) try { - 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; + private bool update_status () { + try { + if (tor_enabled && tor_connected && icon_connected == null) { + 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; + } + if (tor_enabled && !tor_connected && icon_connecting == null) { + 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; + } + if (tor_enabled && icon_enabled == null) { + 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); + } + if (!tor_enabled && icon_disabled == null) { + 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 (tor_enabled && !tor_connected && icon_connecting == null) try { + critical (e.message); 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 && 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); + icon_theme.rescan_if_needed (); + Timeout.add_seconds (1, update_status); + return false; } if (conic_connected && tor_enabled) { @@ -123,6 +127,8 @@ class TorStatusMenuItem : HD.StatusMenuItem { button.set_value (tor_enabled ? _("Disconnected") : _("Disabled")); } button.set_image (tor_enabled ? icon_enabled : icon_disabled); + + return false; } /** @@ -137,12 +143,19 @@ class TorStatusMenuItem : HD.StatusMenuItem { /* var status = */ source.read_line (out line, out length, null); tor_log += line; + if (log_label != null) + log_label.label = tor_log; + 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)); @@ -159,13 +172,74 @@ 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 = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); + + if (bridges.length () > 0) { + // 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"); + } + } + + var exits = gconf.get_list (GCONF_KEY_EXITNODES, GConf.ValueType.STRING); + + if (exits.length () > 0) { + // Enable strict exit nodes + tor_control.set_conf_list ("ExitNodes", exits); + tor_control.set_conf_bool ("StrictExitNodes", true); + + bool strict = yield tor_control.get_conf_bool_async ("StrictExitNodes"); + if (!strict) { + Hildon.Banner.show_information (null, null, + "Failed to set up strict exit nodes"); + } + } + } + + /** * 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 ("\n16:").offset (1).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, @@ -194,6 +268,8 @@ class TorStatusMenuItem : HD.StatusMenuItem { } tor_log = ""; + if (log_label != null) + log_label.label = tor_log; update_status (); } @@ -234,7 +310,7 @@ class TorStatusMenuItem : HD.StatusMenuItem { backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE); } catch (Error e) { - error ("Error saving proxy settings: %s", e.message); + critical ("Error saving proxy settings: %s", e.message); backup = new ProxyBackup (); backup.use_http_proxy = false; @@ -260,7 +336,7 @@ class TorStatusMenuItem : HD.StatusMenuItem { gconf.set_string (GCONF_KEY_PROXY_MODE, "manual"); } catch (Error e) { - error ("Error changing proxy settings: %s", e.message); + critical ("Error changing proxy settings: %s", e.message); } } @@ -282,169 +358,24 @@ class TorStatusMenuItem : HD.StatusMenuItem { gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode); backup = null; } catch (Error e) { - error ("Error restoring proxy: %s", e.message); + critical ("Error restoring proxy: %s", e.message); } } /** * 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) { - error ("Error loading bridges: %s", 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 (); + var dialog = new BridgeDialog (); + dialog.show (); } /** - * Show the bridge relay edit dialog + * Show the exit node configuration 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); - Hildon.gtk_entry_set_input_mode (ip_entry, 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); - Hildon.gtk_entry_set_input_mode (port_entry, 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); - Hildon.gtk_entry_set_input_mode (fingerprint_entry, 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)); - gconf.set_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING, - bridges); - } - 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)); - - bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); - 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)); - } - gconf.set_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING, bridges); - - dialog.destroy (); - } - }); - - dialog.show_all (); + private void exit_nodes_clicked_cb () { + var dialog = new ExitNodeDialog (tor_control); + dialog.show (); } /** @@ -476,10 +407,16 @@ class TorStatusMenuItem : HD.StatusMenuItem { dialog.set_title (_("Log")); var pannable = new Hildon.PannableArea (); - var label = new Gtk.Label (tor_log); - pannable.add_with_viewport (label); + pannable.mov_mode = Hildon.MovementMode.BOTH; + log_label = new Gtk.Label (tor_log); + log_label.set_alignment (0, 0); + pannable.add_with_viewport (log_label); content.pack_start (pannable, true, true, 0); + dialog.response.connect (() => { + log_label = null; + }); + dialog.show_all (); } @@ -490,7 +427,6 @@ class TorStatusMenuItem : HD.StatusMenuItem { 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 (_("Tor: anonymity online")); @@ -508,6 +444,15 @@ class TorStatusMenuItem : HD.StatusMenuItem { button.clicked.connect (bridges_clicked_cb); content.pack_start (button, true, true, 0); + button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT, + Hildon.ButtonArrangement.VERTICAL, + _("Restrict exit nodes"), + get_exit_node_list ()); + button.set_style (Hildon.ButtonStyle.PICKER); + button.set_alignment (0, 0.5f, 0, 0.5f); + button.clicked.connect (exit_nodes_clicked_cb); + content.pack_start (button, true, true, 0); + dialog.add_button (_("Log"), RESPONSE_LOG); dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT); @@ -517,20 +462,18 @@ class TorStatusMenuItem : HD.StatusMenuItem { return; } if (response_id == Gtk.ResponseType.ACCEPT) { - if (!tor_enabled && check.get_active ()) { - tor_enabled = true; + if (!tor_enabled && check.get_active ()) try { + gconf.set_bool (GCONF_KEY_TOR_ENABLED, true); - if (conic_connected) { - start_tor (); - } else { + // Enabled by user interaction, so connect if needed + if (!conic_connected) conic.connect (ConIc.ConnectFlags.NONE); - } - } else if (tor_enabled && !check.get_active ()) { - tor_enabled = false; - - stop_tor (); - if (conic_connected) - conic.disconnect (); + } catch (Error e) { + Hildon.Banner.show_information (null, null, "Failed to enable GConf key"); + } else if (tor_enabled && !check.get_active ()) try { + gconf.set_bool (GCONF_KEY_TOR_ENABLED, false); + } catch (Error e) { + Hildon.Banner.show_information (null, null, "Failed to disable GConf key"); } } dialog.destroy (); @@ -545,7 +488,7 @@ class TorStatusMenuItem : HD.StatusMenuItem { try { bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING); } catch (Error e) { - error ("Error loading bridges: %s", e.message); + critical ("Error loading bridges: %s", e.message); } foreach (string bridge in bridges) { if (list == null) @@ -559,6 +502,48 @@ class TorStatusMenuItem : HD.StatusMenuItem { return list; } + private string get_exit_node_list () { + string list = null; + var exits = new SList (); + try { + exits = gconf.get_list (GCONF_KEY_EXITNODES, GConf.ValueType.STRING); + } catch (Error e) { + error ("Error loading exit nodes: %s", e.message); + } + foreach (string exit in exits) { + if (list == null) + list = exit; + else + list += ", " + exit; + } + if (list == null) + list = _("None"); + + return list; + } + + /** + * Callback for GConf change notification on the tor_enabled key + */ + private void tor_enabled_changed_cb (GConf.Client gc, uint cxnid, GConf.Entry entry) { + if (entry.key == GCONF_KEY_TOR_ENABLED) { + bool old_tor_enabled = tor_enabled; + tor_enabled = entry.get_value ().get_bool (); + if (old_tor_enabled == tor_enabled) + return; + + if (tor_enabled) { + // Start Tor immediately if a connection is already available + if (conic_connected) + start_tor (); + } else { + stop_tor (); + if (conic_connected) + conic.disconnect (); + } + } + } + /** * Callback for the ConIc connection-event signal */ @@ -610,6 +595,8 @@ class TorStatusMenuItem : HD.StatusMenuItem { add (button); + log_label = null; + // Status area icon update_status (); @@ -626,8 +613,12 @@ class TorStatusMenuItem : HD.StatusMenuItem { gconf = GConf.Client.get_default (); try { tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED); + + // Request change notifications for the tor_enabled key + gconf.add_dir (GCONF_DIR_TOR, GConf.ClientPreloadType.ONELEVEL); + gconf.notify_add (GCONF_KEY_TOR_ENABLED, tor_enabled_changed_cb); } catch (Error e) { - error ("Failed to get GConf setting: %s", e.message); + critical ("Failed to get GConf setting: %s", e.message); } tor_connected = false;