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";
41 private const string GCONF_DIR_PROXY_HTTP = "/system/http_proxy";
42 private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
43 private const string GCONF_KEY_PROXY_HTTP_HOST = GCONF_DIR_PROXY_HTTP + "/host";
44 private const string GCONF_KEY_PROXY_HTTP_PORT = GCONF_DIR_PROXY_HTTP + "/port";
46 private const string GCONF_DIR_PROXY = "/system/proxy";
47 private const string GCONF_KEY_PROXY_MODE = GCONF_DIR_PROXY + "/mode";
48 private const string GCONF_KEY_PROXY_SOCKS_HOST = GCONF_DIR_PROXY + "/socks_host";
49 private const string GCONF_KEY_PROXY_SOCKS_PORT = GCONF_DIR_PROXY + "/socks_port";
50 private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
51 private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
58 Gdk.Pixbuf icon_connecting;
59 Gdk.Pixbuf icon_connected;
60 Gtk.Image icon_enabled;
61 Gtk.Image icon_disabled;
63 // ConIc, GConf and Osso context
66 ConIc.Connection conic;
77 TorControl.Connection tor_control;
81 * Update status area icon and status menu button value
83 private bool update_status () {
85 if (tor_enabled && tor_connected && icon_connected == null) {
86 var icon_theme = Gtk.IconTheme.get_default ();
87 var pixbuf = icon_theme.load_icon ("statusarea_tor_connected",
88 STATUS_AREA_ICON_SIZE,
89 Gtk.IconLookupFlags.NO_SVG);
90 icon_connected = pixbuf;
92 if (tor_enabled && !tor_connected && icon_connecting == null) {
93 var icon_theme = Gtk.IconTheme.get_default ();
94 var pixbuf = icon_theme.load_icon ("statusarea_tor_connecting",
95 STATUS_AREA_ICON_SIZE,
96 Gtk.IconLookupFlags.NO_SVG);
97 icon_connecting = pixbuf;
99 if (tor_enabled && icon_enabled == null) {
100 var icon_theme = Gtk.IconTheme.get_default();
101 var pixbuf = icon_theme.load_icon ("statusarea_tor_enabled",
102 STATUS_MENU_ICON_SIZE,
103 Gtk.IconLookupFlags.NO_SVG);
104 icon_enabled = new Gtk.Image.from_pixbuf (pixbuf);
106 if (!tor_enabled && icon_disabled == null) {
107 var icon_theme = Gtk.IconTheme.get_default();
108 var pixbuf = icon_theme.load_icon ("statusarea_tor_disabled",
109 STATUS_MENU_ICON_SIZE,
110 Gtk.IconLookupFlags.NO_SVG);
111 icon_disabled = new Gtk.Image.from_pixbuf (pixbuf);
114 critical (e.message);
115 var icon_theme = Gtk.IconTheme.get_default ();
116 icon_theme.rescan_if_needed ();
117 Timeout.add_seconds (1, update_status);
121 if (conic_connected && tor_enabled) {
122 set_status_area_icon (tor_connected ? icon_connected : icon_connecting);
123 button.set_value (tor_connected ? _("Connected") : _("Connecting ..."));
125 set_status_area_icon (null);
126 button.set_value (tor_enabled ? _("Disconnected") : _("Disabled"));
128 button.set_image (tor_enabled ? icon_enabled : icon_disabled);
134 * Callback for Tor daemon line output
136 private bool tor_io_func (IOChannel source, IOCondition condition) {
138 if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
142 /* var status = */ source.read_line (out line, out length, null);
145 if (log_label != null)
146 log_label.label = tor_log;
148 if ("[notice]" in line) {
149 if ("Bootstrapped 100%" in line) {
150 tor_connected = true;
154 if ("Opening Control listener on 127.0.0.1:9051" in line) {
155 tor_control = new TorControl.Connection ();
156 tor_control_auth.begin ();
160 Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
164 Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
167 if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
174 * Authenticate with Tor on the control channel
176 private async void tor_control_auth () throws Error {
177 yield tor_control.authenticate_async (password);
179 var bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
181 if (bridges.length () > 0) {
182 // Enable bridge relays
183 tor_control.set_conf_list ("Bridge", bridges);
184 tor_control.set_conf_bool ("UseBridges", true);
186 bool use = yield tor_control.get_conf_bool_async ("UseBridges");
188 Hildon.Banner.show_information (null, null,
189 "Failed to set up bridge relays");
195 * Start Tor and setup proxy settings
197 private void start_tor () {
199 if (tor_pid == (Pid) 0) {
200 string[] tor_hash_argv = {
202 "--hash-password", "",
206 Random.set_seed ((uint32) tv.tv_usec);
207 password = "tor-status-%8x".printf (Random.next_int ());
208 tor_hash_argv[2] = password;
210 Process.spawn_sync ("/tmp", tor_hash_argv, null, 0, null, out hash);
211 hash = hash.str ("16:").replace ("\n", "");
214 Hildon.Banner.show_information (null, null,
215 "Failed to get hash");
219 string[] tor_argv = {
221 "--ControlPort", "9051",
222 "--HashedControlPassword", "",
226 Process.spawn_async_with_pipes ("/tmp",
229 SpawnFlags.SEARCH_PATH,
235 var channel = new IOChannel.unix_new (tor_stdout);
236 channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
238 if (polipo_pid == (Pid) 0) {
239 Process.spawn_async_with_pipes ("/tmp",
240 { "/usr/bin/polipo" },
242 SpawnFlags.SEARCH_PATH,
247 /* --> proxy settings and will be set up and tor_connected will
248 * be set to true once Tor signals 100%
250 } catch (SpawnError e) {
251 Hildon.Banner.show_information (null, null, "DEBUG: Failed to spawn polipo and tor: %s".printf (e.message));
256 if (log_label != null)
257 log_label.label = tor_log;
262 * Stop Tor and revert proxy settings
264 private void stop_tor () {
266 tor_connected = false;
267 if (polipo_pid != (Pid) 0) {
268 Process.close_pid (polipo_pid);
269 Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
270 polipo_pid = (Pid) 0;
272 if (tor_pid != (Pid) 0) {
273 Process.close_pid (tor_pid);
274 Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
282 * Setup proxy settings to route through the Tor network
284 private void proxy_setup () {
285 if (backup == null) try {
286 backup = new ProxyBackup ();
287 backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
289 backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
290 backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
291 backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
292 backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
293 backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
294 backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
296 backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
298 critical ("Error saving proxy settings: %s", e.message);
299 backup = new ProxyBackup ();
300 backup.use_http_proxy = false;
302 backup.http_host = "";
303 backup.socks_host = "";
304 backup.secure_host = "";
305 backup.http_port = 8080;
306 backup.socks_port = 0;
307 backup.secure_port = 0;
309 backup.mode = "none";
312 // Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
313 gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
315 gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
316 gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
317 gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
318 gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
319 gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
320 gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
322 gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
324 critical ("Error changing proxy settings: %s", e.message);
329 * Revert proxy settings
331 private void proxy_restore () {
332 if (backup != null) try {
333 // Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
334 gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
336 gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
337 gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
338 gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
339 gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
340 gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
341 gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
343 gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
346 critical ("Error restoring proxy: %s", e.message);
351 * Show the bridge relay configuration dialog
353 private void bridges_clicked_cb () {
354 var dialog = new BridgeDialog ();
359 * Check whether the IP address consists of four numbers in the 0..255 range
361 bool is_valid_ip_address (string address) {
362 string[] ip = address.split (".");
367 for (int i = 0; i < ip.length; i++) {
368 int n = ip[i].to_int ();
369 if (n < 0 || n > 255)
377 * Show the Tor log dialog
379 private void show_tor_log () {
380 var dialog = new Gtk.Dialog ();
381 var content = (Gtk.VBox) dialog.get_content_area ();
382 content.set_size_request (-1, 5*70);
384 dialog.set_title (_("Log"));
386 var pannable = new Hildon.PannableArea ();
387 pannable.mov_mode = Hildon.MovementMode.BOTH;
388 log_label = new Gtk.Label (tor_log);
389 log_label.set_alignment (0, 0);
390 pannable.add_with_viewport (log_label);
391 content.pack_start (pannable, true, true, 0);
393 dialog.response.connect (() => {
401 * Callback for the status menu button clicked signal
403 private const int RESPONSE_LOG = 1;
404 private void button_clicked_cb () {
405 var dialog = new Gtk.Dialog ();
406 var content = (Gtk.VBox) dialog.get_content_area ();
407 content.set_size_request (-1, 2*70);
409 dialog.set_title (_("Tor: anonymity online"));
411 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
412 check.set_label (_("Enable onion routing"));
413 check.set_active (tor_enabled);
414 content.pack_start (check, true, true, 0);
416 var button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
417 Hildon.ButtonArrangement.VERTICAL,
420 button.set_style (Hildon.ButtonStyle.PICKER);
421 button.set_alignment (0, 0.5f, 0, 0.5f);
422 button.clicked.connect (bridges_clicked_cb);
423 content.pack_start (button, true, true, 0);
425 dialog.add_button (_("Log"), RESPONSE_LOG);
427 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
428 dialog.response.connect ((response_id) => {
429 if (response_id == RESPONSE_LOG) {
433 if (response_id == Gtk.ResponseType.ACCEPT) {
434 if (!tor_enabled && check.get_active ()) {
437 if (conic_connected) {
440 conic.connect (ConIc.ConnectFlags.NONE);
442 } else if (tor_enabled && !check.get_active ()) {
456 private string get_bridge_list () {
458 var bridges = new SList<string> ();
460 bridges = gconf.get_list (GCONF_KEY_BRIDGES, GConf.ValueType.STRING);
462 critical ("Error loading bridges: %s", e.message);
464 foreach (string bridge in bridges) {
468 list += ", " + bridge;
477 * Callback for the ConIc connection-event signal
479 private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
480 var status = event.get_status ();
482 case ConIc.ConnectionStatus.CONNECTED:
483 conic_connected = true;
490 case ConIc.ConnectionStatus.DISCONNECTING:
491 conic_connected = false;
494 case ConIc.ConnectionStatus.DISCONNECTED:
495 case ConIc.ConnectionStatus.NETWORK_UP:
500 var error = event.get_error ();
502 case ConIc.ConnectionError.CONNECTION_FAILED:
503 Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
505 case ConIc.ConnectionError.USER_CANCELED:
506 Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
508 case ConIc.ConnectionError.NONE:
509 case ConIc.ConnectionError.INVALID_IAP:
515 private void create_widgets () {
516 // Status menu button
517 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
518 Hildon.ButtonArrangement.VERTICAL,
519 _("The Onion Router"),
520 tor_enabled ? _("Enabled") : _("Disabled"));
521 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
522 button.set_style (Hildon.ButtonStyle.PICKER);
523 button.clicked.connect (button_clicked_cb);
537 Intl.setlocale (LocaleCategory.ALL, "");
538 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
539 Intl.textdomain (Config.GETTEXT_PACKAGE);
542 gconf = GConf.Client.get_default ();
544 tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
546 critical ("Failed to get GConf setting: %s", e.message);
548 tor_connected = false;
551 conic = new ConIc.Connection ();
553 Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
555 conic_connected = false;
556 conic.automatic_connection_events = true;
558 conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
559 conic.connection_event.connect (conic_connection_event_cb);
562 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
572 * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
573 * most of the class registration issues itself. Only this code from
574 * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
575 * to register with hildon-desktop:
578 public void hd_plugin_module_load (TypeModule plugin) {
579 // [ModuleInit] registers types automatically
580 ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
583 public void hd_plugin_module_unload (HD.PluginModule plugin) {