1 /* This file is part of status-area-applet-tor.
3 * Copyright (C) 2010 Philipp Zabel
5 * status-area-applet-tor is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as published
7 * by the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * status-area-applet-tor is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with status-area-applet-tor. If not, see <http://www.gnu.org/licenses/>.
21 public bool use_http_proxy;
22 public string http_host;
23 public string socks_host;
24 public string secure_host;
26 public int socks_port;
27 public int secure_port;
31 class TorStatusMenuItem : HD.StatusMenuItem {
32 private const string STATUSMENU_TOR_LIBOSSO_SERVICE_NAME = "tor_status_menu_item";
34 private const int STATUS_MENU_ICON_SIZE = 48;
35 private const int STATUS_AREA_ICON_SIZE = 18;
37 private const string GCONF_DIR_TOR = "/apps/maemo/tor";
38 private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled";
39 private const string GCONF_KEY_BRIDGES = GCONF_DIR_TOR + "/bridges";
40 private const string GCONF_KEY_EXITNODES = GCONF_DIR_TOR + "/exit_nodes";
42 private const string GCONF_DIR_PROXY_HTTP = "/system/http_proxy";
43 private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
44 private const string GCONF_KEY_PROXY_HTTP_HOST = GCONF_DIR_PROXY_HTTP + "/host";
45 private const string GCONF_KEY_PROXY_HTTP_PORT = GCONF_DIR_PROXY_HTTP + "/port";
47 private const string GCONF_DIR_PROXY = "/system/proxy";
48 private const string GCONF_KEY_PROXY_MODE = GCONF_DIR_PROXY + "/mode";
49 private const string GCONF_KEY_PROXY_SOCKS_HOST = GCONF_DIR_PROXY + "/socks_host";
50 private const string GCONF_KEY_PROXY_SOCKS_PORT = GCONF_DIR_PROXY + "/socks_port";
51 private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
52 private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
59 Gdk.Pixbuf icon_connecting;
60 Gdk.Pixbuf icon_connected;
61 Gtk.Image icon_enabled;
62 Gtk.Image icon_disabled;
64 // ConIc, GConf and Osso context
67 ConIc.Connection conic;
78 TorControl.Connection tor_control;
82 * Update status area icon and status menu button value
84 private bool update_status () {
86 if (tor_enabled && tor_connected && icon_connected == null) {
87 var icon_theme = Gtk.IconTheme.get_default ();
88 var pixbuf = icon_theme.load_icon ("statusarea_tor_connected",
89 STATUS_AREA_ICON_SIZE,
90 Gtk.IconLookupFlags.NO_SVG);
91 icon_connected = pixbuf;
93 if (tor_enabled && !tor_connected && icon_connecting == null) {
94 var icon_theme = Gtk.IconTheme.get_default ();
95 var pixbuf = icon_theme.load_icon ("statusarea_tor_connecting",
96 STATUS_AREA_ICON_SIZE,
97 Gtk.IconLookupFlags.NO_SVG);
98 icon_connecting = pixbuf;
100 if (tor_enabled && icon_enabled == null) {
101 var icon_theme = Gtk.IconTheme.get_default();
102 var pixbuf = icon_theme.load_icon ("statusarea_tor_enabled",
103 STATUS_MENU_ICON_SIZE,
104 Gtk.IconLookupFlags.NO_SVG);
105 icon_enabled = new Gtk.Image.from_pixbuf (pixbuf);
107 if (!tor_enabled && icon_disabled == null) {
108 var icon_theme = Gtk.IconTheme.get_default();
109 var pixbuf = icon_theme.load_icon ("statusarea_tor_disabled",
110 STATUS_MENU_ICON_SIZE,
111 Gtk.IconLookupFlags.NO_SVG);
112 icon_disabled = new Gtk.Image.from_pixbuf (pixbuf);
115 critical (e.message);
116 var icon_theme = Gtk.IconTheme.get_default ();
117 icon_theme.rescan_if_needed ();
118 Timeout.add_seconds (1, update_status);
122 if (conic_connected && tor_enabled) {
123 set_status_area_icon (tor_connected ? icon_connected : icon_connecting);
124 button.set_value (tor_connected ? _("Connected") : _("Connecting ..."));
126 set_status_area_icon (null);
127 button.set_value (tor_enabled ? _("Disconnected") : _("Disabled"));
129 button.set_image (tor_enabled ? icon_enabled : icon_disabled);
135 * Callback for Tor daemon line output
137 private bool tor_io_func (IOChannel source, IOCondition condition) {
139 if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
143 /* var status = */ source.read_line (out line, out length, null);
146 if (log_label != null)
147 log_label.label = tor_log;
149 if ("[notice]" in line) {
150 if ("Bootstrapped 100%" in line) {
151 tor_connected = true;
155 if ("Opening Control listener on 127.0.0.1:9051" in line) {
156 tor_control = new TorControl.Connection ();
157 tor_control_auth.begin ();
161 Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
165 Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
168 if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
175 * Authenticate with Tor on the control channel
177 private async void tor_control_auth () throws Error {
178 yield tor_control.authenticate_async (password);
180 var bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
182 if (bridges.length () > 0) {
183 // Enable bridge relays
184 tor_control.set_conf_list ("Bridge", bridges);
185 tor_control.set_conf_bool ("UseBridges", true);
187 bool use = yield tor_control.get_conf_bool_async ("UseBridges");
189 Hildon.Banner.show_information (null, null,
190 "Failed to set up bridge relays");
194 var exits = gconf.get_list (GCONF_KEY_EXITNODES, GConf.ValueType.STRING);
196 if (exits.length () > 0) {
197 // Enable strict exit nodes
198 tor_control.set_conf_list ("ExitNodes", exits);
199 tor_control.set_conf_bool ("StrictExitNodes", true);
201 bool strict = yield tor_control.get_conf_bool_async ("StrictExitNodes");
203 Hildon.Banner.show_information (null, null,
204 "Failed to set up strict exit nodes");
210 * Start Tor and setup proxy settings
212 private void start_tor () {
214 if (tor_pid == (Pid) 0) {
215 string[] tor_hash_argv = {
217 "--hash-password", "",
221 Random.set_seed ((uint32) tv.tv_usec);
222 password = "tor-status-%8x".printf (Random.next_int ());
223 tor_hash_argv[2] = password;
225 Process.spawn_sync ("/tmp", tor_hash_argv, null, 0, null, out hash);
226 hash = hash.str ("\n16:").offset (1).replace ("\n", "");
229 Hildon.Banner.show_information (null, null,
230 "Failed to get hash");
234 string[] tor_argv = {
236 "--ControlPort", "9051",
237 "--HashedControlPassword", "",
241 Process.spawn_async_with_pipes ("/tmp",
244 SpawnFlags.SEARCH_PATH,
250 var channel = new IOChannel.unix_new (tor_stdout);
251 channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
253 if (polipo_pid == (Pid) 0) {
254 Process.spawn_async_with_pipes ("/tmp",
255 { "/usr/bin/polipo" },
257 SpawnFlags.SEARCH_PATH,
262 /* --> proxy settings and will be set up and tor_connected will
263 * be set to true once Tor signals 100%
265 } catch (SpawnError e) {
266 Hildon.Banner.show_information (null, null, "DEBUG: Failed to spawn polipo and tor: %s".printf (e.message));
271 if (log_label != null)
272 log_label.label = tor_log;
277 * Stop Tor and revert proxy settings
279 private void stop_tor () {
281 tor_connected = false;
282 if (polipo_pid != (Pid) 0) {
283 Process.close_pid (polipo_pid);
284 Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
285 polipo_pid = (Pid) 0;
287 if (tor_pid != (Pid) 0) {
288 Process.close_pid (tor_pid);
289 Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
297 * Setup proxy settings to route through the Tor network
299 private void proxy_setup () {
300 if (backup == null) try {
301 backup = new ProxyBackup ();
302 backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
304 backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
305 backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
306 backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
307 backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
308 backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
309 backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
311 backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
313 critical ("Error saving proxy settings: %s", e.message);
314 backup = new ProxyBackup ();
315 backup.use_http_proxy = false;
317 backup.http_host = "";
318 backup.socks_host = "";
319 backup.secure_host = "";
320 backup.http_port = 8080;
321 backup.socks_port = 0;
322 backup.secure_port = 0;
324 backup.mode = "none";
327 // Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
328 gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
330 gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
331 gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
332 gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
333 gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
334 gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
335 gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
337 gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
339 critical ("Error changing proxy settings: %s", e.message);
344 * Revert proxy settings
346 private void proxy_restore () {
347 if (backup != null) try {
348 // Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
349 gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
351 gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
352 gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
353 gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
354 gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
355 gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
356 gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
358 gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
361 critical ("Error restoring proxy: %s", e.message);
366 * Show the bridge relay configuration dialog
368 private void bridges_clicked_cb () {
369 var dialog = new BridgeDialog ();
374 * Show the exit node configuration dialog
376 private void exit_nodes_clicked_cb () {
377 var dialog = new ExitNodeDialog (tor_control);
382 * Check whether the IP address consists of four numbers in the 0..255 range
384 bool is_valid_ip_address (string address) {
385 string[] ip = address.split (".");
390 for (int i = 0; i < ip.length; i++) {
391 int n = ip[i].to_int ();
392 if (n < 0 || n > 255)
400 * Show the Tor log dialog
402 private void show_tor_log () {
403 var dialog = new Gtk.Dialog ();
404 var content = (Gtk.VBox) dialog.get_content_area ();
405 content.set_size_request (-1, 5*70);
407 dialog.set_title (_("Log"));
409 var pannable = new Hildon.PannableArea ();
410 pannable.mov_mode = Hildon.MovementMode.BOTH;
411 log_label = new Gtk.Label (tor_log);
412 log_label.set_alignment (0, 0);
413 pannable.add_with_viewport (log_label);
414 content.pack_start (pannable, true, true, 0);
416 dialog.response.connect (() => {
424 * Callback for the status menu button clicked signal
426 private const int RESPONSE_LOG = 1;
427 private void button_clicked_cb () {
428 var dialog = new Gtk.Dialog ();
429 var content = (Gtk.VBox) dialog.get_content_area ();
430 content.set_size_request (-1, 3*70);
432 dialog.set_title (_("Tor: anonymity online"));
434 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
435 check.set_label (_("Enable onion routing"));
436 check.set_active (tor_enabled);
437 content.pack_start (check, true, true, 0);
439 var button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
440 Hildon.ButtonArrangement.VERTICAL,
443 button.set_style (Hildon.ButtonStyle.PICKER);
444 button.set_alignment (0, 0.5f, 0, 0.5f);
445 button.clicked.connect (bridges_clicked_cb);
446 content.pack_start (button, true, true, 0);
448 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
449 Hildon.ButtonArrangement.VERTICAL,
450 _("Restrict exit nodes"),
451 get_exit_node_list ());
452 button.set_style (Hildon.ButtonStyle.PICKER);
453 button.set_alignment (0, 0.5f, 0, 0.5f);
454 button.clicked.connect (exit_nodes_clicked_cb);
455 content.pack_start (button, true, true, 0);
457 dialog.add_button (_("Log"), RESPONSE_LOG);
459 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
460 dialog.response.connect ((response_id) => {
461 if (response_id == RESPONSE_LOG) {
465 if (response_id == Gtk.ResponseType.ACCEPT) {
466 if (!tor_enabled && check.get_active ()) {
469 if (conic_connected) {
472 conic.connect (ConIc.ConnectFlags.NONE);
474 } else if (tor_enabled && !check.get_active ()) {
488 private string get_bridge_list () {
490 var bridges = new SList<string> ();
492 bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
494 critical ("Error loading bridges: %s", e.message);
496 foreach (string bridge in bridges) {
500 list += ", " + bridge;
508 private string get_exit_node_list () {
510 var exits = new SList<string> ();
512 exits = gconf.get_list (GCONF_KEY_EXITNODES, GConf.ValueType.STRING);
514 error ("Error loading exit nodes: %s", e.message);
516 foreach (string exit in exits) {
529 * Callback for the ConIc connection-event signal
531 private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
532 var status = event.get_status ();
534 case ConIc.ConnectionStatus.CONNECTED:
535 conic_connected = true;
542 case ConIc.ConnectionStatus.DISCONNECTING:
543 conic_connected = false;
546 case ConIc.ConnectionStatus.DISCONNECTED:
547 case ConIc.ConnectionStatus.NETWORK_UP:
552 var error = event.get_error ();
554 case ConIc.ConnectionError.CONNECTION_FAILED:
555 Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
557 case ConIc.ConnectionError.USER_CANCELED:
558 Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
560 case ConIc.ConnectionError.NONE:
561 case ConIc.ConnectionError.INVALID_IAP:
567 private void create_widgets () {
568 // Status menu button
569 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
570 Hildon.ButtonArrangement.VERTICAL,
571 _("The Onion Router"),
572 tor_enabled ? _("Enabled") : _("Disabled"));
573 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
574 button.set_style (Hildon.ButtonStyle.PICKER);
575 button.clicked.connect (button_clicked_cb);
589 Intl.setlocale (LocaleCategory.ALL, "");
590 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
591 Intl.textdomain (Config.GETTEXT_PACKAGE);
594 gconf = GConf.Client.get_default ();
596 tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
598 critical ("Failed to get GConf setting: %s", e.message);
600 tor_connected = false;
603 conic = new ConIc.Connection ();
605 Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
607 conic_connected = false;
608 conic.automatic_connection_events = true;
610 conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
611 conic.connection_event.connect (conic_connection_event_cb);
614 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
624 * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
625 * most of the class registration issues itself. Only this code from
626 * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
627 * to register with hildon-desktop:
630 public void hd_plugin_module_load (TypeModule plugin) {
631 // [ModuleInit] registers types automatically
632 ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
635 public void hd_plugin_module_unload (HD.PluginModule plugin) {