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