8461817f63cc0d569c4d1fa3d86d7db90177c853
[tor-status] / src / status-area-applet-tor.vala
1 /* This file is part of status-area-applet-tor.
2  *
3  * Copyright (C) 2010 Philipp Zabel
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 [Compact]
20 class ProxyBackup {
21         public bool use_http_proxy;
22         public string http_host;
23         public string socks_host;
24         public string secure_host;
25         public int http_port;
26         public int socks_port;
27         public int secure_port;
28         public string mode;
29 }
30
31 class TorStatusMenuItem : HD.StatusMenuItem {
32         private const string STATUSMENU_TOR_LIBOSSO_SERVICE_NAME = "tor_status_menu_item";
33
34         private const int STATUS_MENU_ICON_SIZE = 48;
35         private const int STATUS_AREA_ICON_SIZE = 18;
36
37         private const string GCONF_DIR_TOR         = "/apps/maemo/tor";
38         private const string GCONF_KEY_TOR_ENABLED = GCONF_DIR_TOR + "/enabled";
39
40         private const string GCONF_DIR_PROXY_HTTP         = "/system/http_proxy";
41         private const string GCONF_KEY_PROXY_HTTP_ENABLED = GCONF_DIR_PROXY_HTTP + "/use_http_proxy";
42         private const string GCONF_KEY_PROXY_HTTP_HOST    = GCONF_DIR_PROXY_HTTP + "/host";
43         private const string GCONF_KEY_PROXY_HTTP_PORT    = GCONF_DIR_PROXY_HTTP + "/port";
44
45         private const string GCONF_DIR_PROXY             = "/system/proxy";
46         private const string GCONF_KEY_PROXY_MODE        = GCONF_DIR_PROXY + "/mode";
47         private const string GCONF_KEY_PROXY_SOCKS_HOST  = GCONF_DIR_PROXY + "/socks_host";
48         private const string GCONF_KEY_PROXY_SOCKS_PORT  = GCONF_DIR_PROXY + "/socks_port";
49         private const string GCONF_KEY_PROXY_SECURE_HOST = GCONF_DIR_PROXY + "/secure_host";
50         private const string GCONF_KEY_PROXY_SECURE_PORT = GCONF_DIR_PROXY + "/secure_port";
51
52         // Widgets
53         Hildon.Button button;
54
55         // Icons
56         Gdk.Pixbuf icon_connecting;
57         Gdk.Pixbuf icon_connected;
58         Gtk.Image icon_enabled;
59         Gtk.Image icon_disabled;
60
61         // ConIc, GConf and Osso context
62         Osso.Context osso;
63         GConf.Client gconf;
64         ConIc.Connection conic;
65         bool conic_connected;
66
67         // Internal state
68         bool tor_enabled;
69         bool tor_connected;
70         Pid tor_pid;
71         int tor_stdout;
72         Pid polipo_pid;
73         ProxyBackup backup;
74
75         /**
76          * Update status area icon and status menu button value
77          */
78         private void update_status () {
79                 if (tor_enabled && tor_connected && icon_connected == null) try {
80                         var icon_theme = Gtk.IconTheme.get_default ();
81                         var pixbuf = icon_theme.load_icon ("statusarea_tor_connected",
82                                                            STATUS_AREA_ICON_SIZE,
83                                                            Gtk.IconLookupFlags.NO_SVG);
84                         icon_connected = pixbuf;
85                 } catch (Error e) {
86                         error (e.message);
87                 }
88                 if (tor_enabled && !tor_connected && icon_connecting == null) try {
89                         var icon_theme = Gtk.IconTheme.get_default ();
90                         var pixbuf = icon_theme.load_icon ("statusarea_tor_connecting",
91                                                            STATUS_AREA_ICON_SIZE,
92                                                            Gtk.IconLookupFlags.NO_SVG);
93                         icon_connecting = pixbuf;
94                 } catch (Error e) {
95                         error (e.message);
96                 }
97                 if (tor_enabled && icon_enabled == null) try {
98                         var icon_theme = Gtk.IconTheme.get_default();
99                         var pixbuf = icon_theme.load_icon ("statusarea_tor_enabled",
100                                                            STATUS_MENU_ICON_SIZE,
101                                                            Gtk.IconLookupFlags.NO_SVG);
102                         icon_enabled = new Gtk.Image.from_pixbuf (pixbuf);
103                 } catch (Error e) {
104                         error (e.message);
105                 }
106                 if (!tor_enabled && icon_disabled == null) try {
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);
112                 } catch (Error e) {
113                         error (e.message);
114                 }
115
116                 if (conic_connected && tor_enabled) {
117                         set_status_area_icon (tor_connected ? icon_connected : icon_connecting);
118                         button.set_value (tor_connected ? _("Connected") : _("Connecting ..."));
119                 } else {
120                         set_status_area_icon (null);
121                         button.set_value (tor_enabled ? _("Disconnected") : _("Disabled"));
122                 }
123                 button.set_image (tor_enabled ? icon_enabled : icon_disabled);
124         }
125
126         /**
127          * Callback for Tor daemon line output
128          */
129         private bool tor_io_func (IOChannel source, IOCondition condition) {
130
131                 if ((condition & (IOCondition.IN | IOCondition.PRI)) != 0) {
132                         string line = null;
133                         size_t length;
134                         try {
135                                 /* var status = */ source.read_line (out line, out length, null);
136
137                                 if ("[notice]" in line) {
138                                         if ("Bootstrapped 100%" in line) {
139                                                 tor_connected = true;
140                                                 proxy_setup ();
141                                                 update_status ();
142                                         }
143                                 } else {
144                                         // FIXME
145                                         Hildon.Banner.show_information (null, null, "DEBUG: %s".printf (line));
146                                 }
147                         } catch (Error e) {
148                                 // FIXME
149                                 Hildon.Banner.show_information (null, null, "Error: %s".printf (e.message));
150                         }
151                 }
152                 if ((condition & (IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL)) != 0) {
153                         return false;
154                 }
155                 return true;
156         }
157
158         /**
159          * Start Tor and setup proxy settings
160          */
161         private void start_tor () {
162                 try {
163                         if (tor_pid == (Pid) 0) {
164                                 Process.spawn_async_with_pipes ("/tmp",
165                                                                 { "/usr/sbin/tor" },
166                                                                 null,
167                                                                 SpawnFlags.SEARCH_PATH,
168                                                                 null,
169                                                                 out tor_pid,
170                                                                 null,
171                                                                 out tor_stdout);
172
173                                 var channel = new IOChannel.unix_new (tor_stdout);
174                                 channel.add_watch (IOCondition.IN | IOCondition.PRI | IOCondition.ERR | IOCondition.HUP | IOCondition.NVAL, tor_io_func);
175                         }
176                         if (polipo_pid == (Pid) 0) {
177                                 Process.spawn_async_with_pipes ("/tmp",
178                                                                 { "/usr/bin/polipo" },
179                                                                 null,
180                                                                 SpawnFlags.SEARCH_PATH,
181                                                                 null,
182                                                                 out polipo_pid);
183                         }
184
185                         /* --> proxy settings and will be set up and tor_connected will
186                          * be set to true once Tor signals 100%
187                          */
188                 } catch (SpawnError e) {
189                         error ("Failed to spawn polipo and tor: %s", e.message);
190                         return;
191                 }
192
193                 update_status ();
194         }
195
196         /**
197          * Stop Tor and revert proxy settings
198          */
199         private void stop_tor () {
200                 proxy_restore ();
201                 tor_connected = false;
202                 if (polipo_pid != (Pid) 0) {
203                         Process.close_pid (polipo_pid);
204                         Posix.kill ((Posix.pid_t) polipo_pid, Posix.SIGKILL);
205                         polipo_pid = (Pid) 0;
206                 }
207                 if (tor_pid != (Pid) 0) {
208                         Process.close_pid (tor_pid);
209                         Posix.kill ((Posix.pid_t) tor_pid, Posix.SIGKILL);
210                         tor_pid = (Pid) 0;
211                 }
212
213                 update_status ();
214         }
215
216         /**
217          * Setup proxy settings to route through the Tor network
218          */
219         private void proxy_setup () {
220                 if (backup == null) try {
221                         backup = new ProxyBackup ();
222                         backup.use_http_proxy = gconf.get_bool (GCONF_KEY_PROXY_HTTP_ENABLED);
223
224                         backup.http_host = gconf.get_string (GCONF_KEY_PROXY_HTTP_HOST);
225                         backup.socks_host = gconf.get_string (GCONF_KEY_PROXY_SOCKS_HOST);
226                         backup.secure_host = gconf.get_string (GCONF_KEY_PROXY_SECURE_HOST);
227                         backup.http_port = gconf.get_int (GCONF_KEY_PROXY_HTTP_PORT);
228                         backup.socks_port = gconf.get_int (GCONF_KEY_PROXY_SOCKS_PORT);
229                         backup.secure_port = gconf.get_int (GCONF_KEY_PROXY_SECURE_PORT);
230
231                         backup.mode = gconf.get_string (GCONF_KEY_PROXY_MODE);
232                 } catch (Error e) {
233                         error ("Error saving proxy settings: %s", e.message);
234                         backup = new ProxyBackup ();
235                         backup.use_http_proxy = false;
236
237                         backup.http_host = "";
238                         backup.socks_host = "";
239                         backup.secure_host = "";
240                         backup.http_port = 8080;
241                         backup.socks_port = 0;
242                         backup.secure_port = 0;
243
244                         backup.mode = "none";
245                 }
246                 try {
247                 //      Hildon.Banner.show_information (null, null, "DEBUG: Proxy setup");
248                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, true);
249
250                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, "127.0.0.1");
251                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, "127.0.0.1");
252                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, "127.0.0.1");
253                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, 8118);
254                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, 9050);
255                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, 8118);
256
257                         gconf.set_string (GCONF_KEY_PROXY_MODE, "manual");
258                 } catch (Error e) {
259                         error ("Error changing proxy settings: %s", e.message);
260                 }
261         }
262
263         /**
264          * Revert proxy settings
265          */
266         private void proxy_restore () {
267                 if (backup != null) try {
268                 //      Hildon.Banner.show_information (null, null, "DEBUG: Restoring proxy settings");
269                         gconf.set_bool (GCONF_KEY_PROXY_HTTP_ENABLED, backup.use_http_proxy);
270
271                         gconf.set_string (GCONF_KEY_PROXY_HTTP_HOST, backup.http_host);
272                         gconf.set_string (GCONF_KEY_PROXY_SOCKS_HOST, backup.socks_host);
273                         gconf.set_string (GCONF_KEY_PROXY_SECURE_HOST, backup.secure_host);
274                         gconf.set_int (GCONF_KEY_PROXY_HTTP_PORT, backup.http_port);
275                         gconf.set_int (GCONF_KEY_PROXY_SOCKS_PORT, backup.socks_port);
276                         gconf.set_int (GCONF_KEY_PROXY_SECURE_PORT, backup.secure_port);
277
278                         gconf.set_string (GCONF_KEY_PROXY_MODE, backup.mode);
279                         backup = null;
280                 } catch (Error e) {
281                         error ("Error restoring proxy: %s", e.message);
282                 }
283         }
284
285         /**
286          * Callback for the status menu button clicked signal
287          */
288         private void button_clicked_cb () {
289                 var dialog = new Gtk.Dialog ();
290                 var content = (Gtk.VBox) dialog.get_content_area ();
291
292                 dialog.set_title (_("Tor: anonymity online"));
293
294                 var check = new Hildon.CheckButton (Hildon.SizeType.FINGER_HEIGHT);
295                 check.set_label (_("Enable onion routing"));
296                 check.set_active (tor_enabled);
297                 content.pack_start (check, true, true, 0);
298
299                 dialog.add_button (_("Save"), Gtk.ResponseType.ACCEPT);
300                 dialog.response.connect ((response_id) => {
301                         if (response_id == Gtk.ResponseType.ACCEPT) {
302                                 if (!tor_enabled && check.get_active ()) {
303                                         tor_enabled = true;
304
305                                         if (conic_connected) {
306                                                 start_tor ();
307                                         } else {
308                                                 conic.connect (ConIc.ConnectFlags.NONE);
309                                         }
310                                 } else if (tor_enabled && !check.get_active ()) {
311                                         tor_enabled = false;
312
313                                         stop_tor ();
314                                         if (conic_connected)
315                                                 conic.disconnect ();
316                                 }
317                         }
318                         dialog.destroy ();
319                 });
320
321                 dialog.show_all ();
322         }
323
324         /**
325          * Callback for the ConIc connection-event signal
326          */
327         private void conic_connection_event_cb (ConIc.Connection conic, ConIc.ConnectionEvent event) {
328                 var status = event.get_status ();
329                 switch (status) {
330                 case ConIc.ConnectionStatus.CONNECTED:
331                         conic_connected = true;
332                         if (tor_enabled) {
333                                 start_tor ();
334                         } else {
335                                 update_status ();
336                         }
337                         break;
338                 case ConIc.ConnectionStatus.DISCONNECTING:
339                         conic_connected = false;
340                         stop_tor ();
341                         break;
342                 case ConIc.ConnectionStatus.DISCONNECTED:
343                 case ConIc.ConnectionStatus.NETWORK_UP:
344                         // ignore
345                         break;
346                 }
347
348                 var error = event.get_error ();
349                 switch (error) {
350                 case ConIc.ConnectionError.CONNECTION_FAILED:
351                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc connection failed");
352                         break;
353                 case ConIc.ConnectionError.USER_CANCELED:
354                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc user canceled");
355                         break;
356                 case ConIc.ConnectionError.NONE:
357                 case ConIc.ConnectionError.INVALID_IAP:
358                         // ignore
359                         break;
360                 }
361         }
362
363         private void create_widgets () {
364                 // Status menu button
365                 button = new Hildon.Button.with_text (Hildon.SizeType.FINGER_HEIGHT,
366                                                       Hildon.ButtonArrangement.VERTICAL,
367                                                       _("The Onion Router"),
368                                                       tor_enabled ? _("Enabled") : _("Disabled"));
369                 button.set_alignment (0.0f, 0.5f, 1.0f, 1.0f);
370                 button.set_style (Hildon.ButtonStyle.PICKER);
371                 button.clicked.connect (button_clicked_cb);
372
373                 add (button);
374
375                 // Status area icon
376                 update_status ();
377
378                 show_all ();
379         }
380
381         construct {
382                 // Gettext hook-up
383                 Intl.setlocale (LocaleCategory.ALL, "");
384                 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
385                 Intl.textdomain (Config.GETTEXT_PACKAGE);
386
387                 // GConf hook-up
388                 gconf = GConf.Client.get_default ();
389                 try {
390                         tor_enabled = gconf.get_bool (GCONF_KEY_TOR_ENABLED);
391                 } catch (Error e) {
392                         error ("Failed to get GConf setting: %s", e.message);
393                 }
394                 tor_connected = false;
395
396                 // ConIc hook-up
397                 conic = new ConIc.Connection ();
398                 if (conic == null) {
399                         Hildon.Banner.show_information (null, null, "DEBUG: ConIc hook-up failed");
400                 }
401                 conic_connected = false;
402                 conic.automatic_connection_events = true;
403                 if (tor_enabled)
404                         conic.connect (ConIc.ConnectFlags.AUTOMATICALLY_TRIGGERED);
405                 conic.connection_event.connect (conic_connection_event_cb);
406
407                 // Osso hook-up
408                 osso = new Osso.Context (STATUSMENU_TOR_LIBOSSO_SERVICE_NAME,
409                                          Config.VERSION,
410                                          true,
411                                          null);
412
413                 create_widgets ();
414         }
415 }
416
417 /**
418  * Vala code can't use the HD_DEFINE_PLUGIN_MODULE macro, but it handles
419  * most of the class registration issues itself. Only this code from
420  * HD_PLUGIN_MODULE_SYMBOLS_CODE has to be has to be included manually
421  * to register with hildon-desktop:
422  */
423 [ModuleInit]
424 public void hd_plugin_module_load (TypeModule plugin) {
425         // [ModuleInit] registers types automatically
426         ((HD.PluginModule) plugin).add_type (typeof (TorStatusMenuItem));
427 }
428
429 public void hd_plugin_module_unload (HD.PluginModule plugin) {
430 }