Maemo: Handle status changes from charon.
[strongswan.git] / src / frontends / maemo / src / strongswan-status.c
1 /*
2 * Copyright (C) 2010 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include <hildon/hildon.h>
17 #include <libosso.h>
18
19 #include <string.h>
20
21 #include "strongswan-status.h"
22 #include "strongswan-connections.h"
23
24 #define STRONGSWAN_STATUS_GET_PRIVATE(object) \
25 (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
26 STRONGSWAN_TYPE_STATUS, \
27 StrongswanStatusPrivate))
28
29 #define OSSO_STATUS_NAME "status"
30 #define OSSO_STATUS_SERVICE "org.strongswan."OSSO_STATUS_NAME
31 #define OSSO_STATUS_OBJECT "/org/strongswan/"OSSO_STATUS_NAME
32 #define OSSO_STATUS_IFACE "org.strongswan."OSSO_STATUS_NAME
33
34 #define OSSO_CHARON_NAME "charon"
35 #define OSSO_CHARON_SERVICE "org.strongswan."OSSO_CHARON_NAME
36 #define OSSO_CHARON_OBJECT "/org/strongswan/"OSSO_CHARON_NAME
37 #define OSSO_CHARON_IFACE "org.strongswan."OSSO_CHARON_NAME
38
39 #define ICON_SIZE_STATUS 18
40 #define ICON_SIZE_BUTTON 48
41
42 typedef enum
43 {
44 STATUS_DISCONNECTED,
45 STATUS_CONNECTING,
46 STATUS_CONNECTED,
47 STATUS_AUTH_FAILED,
48 STATUS_CONNECTION_FAILED,
49 } StrongswanConnectionStatus;
50
51 struct _StrongswanStatusPrivate
52 {
53 struct {
54 GdkPixbuf *status_open;
55 GdkPixbuf *status_close;
56 GdkPixbuf *button_open;
57 GdkPixbuf *button_close;
58 } icons;
59
60 GtkWidget *dialog;
61 GtkWidget *button;
62 GtkWidget *image;
63 GtkWidget *selector;
64 GtkWidget *box;
65
66 osso_context_t *context;
67
68 StrongswanConnections *conns;
69
70 StrongswanConnectionStatus status;
71 gchar *current;
72 };
73
74 HD_DEFINE_PLUGIN_MODULE_EXTENDED (StrongswanStatus, strongswan_status, \
75 HD_TYPE_STATUS_MENU_ITEM, {}, { \
76 strongswan_connection_register (G_TYPE_MODULE (plugin)); \
77 strongswan_connections_register (G_TYPE_MODULE (plugin)); }, {});
78
79 static void
80 update_status_menu (StrongswanStatus *plugin)
81 {
82 StrongswanStatusPrivate *priv = plugin->priv;
83 switch (priv->status)
84 {
85 default:
86 case STATUS_DISCONNECTED:
87 {
88 hildon_button_set_value (HILDON_BUTTON (priv->button),
89 "Not connected");
90 hd_status_plugin_item_set_status_area_icon (
91 HD_STATUS_PLUGIN_ITEM (plugin),
92 priv->icons.status_open);
93 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image),
94 priv->icons.button_open);
95 break;
96 }
97 case STATUS_CONNECTING:
98 {
99 gchar *msg = g_strdup_printf ("Connecting to %s...", priv->current);
100 hildon_button_set_value (HILDON_BUTTON (priv->button), msg);
101 g_free (msg);
102 break;
103 }
104 case STATUS_CONNECTED:
105 {
106 gchar *msg = g_strdup_printf ("Connected to %s", priv->current);
107 hildon_button_set_value (HILDON_BUTTON (priv->button), msg);
108 g_free (msg);
109 hd_status_plugin_item_set_status_area_icon (
110 HD_STATUS_PLUGIN_ITEM (plugin),
111 priv->icons.status_close);
112 gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image),
113 priv->icons.button_close);
114 break;
115 }
116 }
117 }
118
119 static void
120 update_dialog_connecting (StrongswanStatus *plugin)
121 {
122 StrongswanStatusPrivate *priv = plugin->priv;
123
124 gtk_widget_set_sensitive (priv->box, FALSE);
125 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (priv->dialog), 1);
126 }
127
128 static void
129 update_dialog_default (StrongswanStatus *plugin)
130 {
131 StrongswanStatusPrivate *priv = plugin->priv;
132
133 gtk_widget_set_sensitive (priv->box, TRUE);
134 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (priv->dialog), 0);
135 }
136
137 static void
138 dialog_response (GtkDialog *dialog, gint response_id, StrongswanStatus *plugin)
139 {
140 StrongswanStatusPrivate *priv = plugin->priv;
141 gtk_widget_destroy (priv->dialog);
142 priv->dialog = NULL;
143 }
144
145 static void
146 handle_status_change (StrongswanStatus *plugin,
147 StrongswanConnectionStatus status,
148 gchar *message)
149 {
150 StrongswanStatusPrivate *priv = plugin->priv;
151 gchar *msg = NULL;
152
153 switch (status)
154 {
155 case STATUS_CONNECTION_FAILED:
156 {
157 if (priv->status == STATUS_CONNECTED)
158 {
159 msg = g_strdup_printf ("Lost connection to %s", priv->current);
160 }
161 else if (message)
162 {
163 msg = g_strdup_printf ("Failed to connect to %s: %s",
164 priv->current, message);
165 }
166 else
167 {
168 msg = g_strdup_printf ("Failed to connect to %s",
169 priv->current);
170 }
171 status = STATUS_DISCONNECTED;
172 break;
173 }
174 case STATUS_AUTH_FAILED:
175 {
176 msg = g_strdup_printf ("Failed to connect to %s: authentication "
177 "failed", priv->current);
178 status = STATUS_DISCONNECTED;
179 /* TODO: show password dialog again? */
180 break;
181 }
182 case STATUS_CONNECTED:
183 {
184 msg = g_strdup_printf ("Successfully connected to %s",
185 priv->current);
186 break;
187 }
188 default:
189 case STATUS_DISCONNECTED:
190 {
191 msg = g_strdup_printf ("Disconnected from %s", priv->current);
192 break;
193 }
194 }
195
196 priv->status = status;
197
198 if (status == STATUS_DISCONNECTED)
199 {
200 priv->current = (g_free (priv->current), NULL);
201 }
202
203 hildon_banner_show_information (NULL, NULL, msg);
204 g_free (msg);
205
206 update_status_menu (plugin);
207
208 if (priv->dialog)
209 {
210 update_dialog_default (plugin);
211 if (status == STATUS_CONNECTED)
212 {
213 gtk_dialog_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_OK);
214 }
215 }
216 }
217
218 static gboolean
219 get_password (StrongswanStatus *plugin, gchar **password)
220 {
221 StrongswanStatusPrivate *priv = plugin->priv;
222 gboolean result = FALSE;
223
224 GtkWidget *dialog = gtk_dialog_new_with_buttons (
225 "Connecting...",
226 GTK_WINDOW(priv->dialog),
227 GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
228 GTK_STOCK_CANCEL,
229 GTK_RESPONSE_CANCEL,
230 GTK_STOCK_OK,
231 GTK_RESPONSE_OK,
232 NULL);
233 GtkWidget *vbox = GTK_DIALOG (dialog)->vbox;
234 GtkSizeGroup *group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
235
236 GtkWidget *pass = hildon_entry_new (HILDON_SIZE_AUTO);
237 hildon_gtk_entry_set_placeholder_text (GTK_ENTRY (pass), "Password");
238 hildon_gtk_entry_set_input_mode (GTK_ENTRY (pass),
239 HILDON_GTK_INPUT_MODE_FULL |
240 HILDON_GTK_INPUT_MODE_INVISIBLE);
241 GtkWidget *pcap = hildon_caption_new (group,
242 "Password",
243 pass,
244 NULL,
245 HILDON_CAPTION_OPTIONAL);
246 gtk_box_pack_start (GTK_BOX (vbox), pcap, TRUE, TRUE, 0);
247 gtk_widget_show_all (dialog);
248
249 gint retval = gtk_dialog_run (GTK_DIALOG (dialog));
250 if (retval == GTK_RESPONSE_OK)
251 {
252 *password = g_strdup (gtk_entry_get_text (GTK_ENTRY (pass)));
253 result = TRUE;
254 }
255 gtk_widget_destroy (dialog);
256 return result;
257 }
258
259 static void
260 connect_clicked (HildonButton *button, StrongswanStatus *plugin)
261 {
262 StrongswanStatusPrivate *priv = plugin->priv;
263 osso_return_t result;
264 osso_rpc_t retval;
265
266 priv->current = hildon_touch_selector_get_current_text (
267 HILDON_TOUCH_SELECTOR (priv->selector));
268 priv->status = STATUS_CONNECTING;
269 update_dialog_connecting (plugin);
270 update_status_menu (plugin);
271
272 StrongswanConnection *conn = strongswan_connections_get_connection (
273 priv->conns,
274 priv->current);
275 if (!conn)
276 {
277 handle_status_change (plugin, STATUS_CONNECTION_FAILED, "not found");
278 return;
279 }
280
281 /* this call on the system bus is only needed to start charon as root */
282 result = osso_rpc_run_system (priv->context,
283 OSSO_CHARON_SERVICE,
284 OSSO_CHARON_OBJECT,
285 OSSO_CHARON_IFACE,
286 "Start",
287 &retval,
288 DBUS_TYPE_INVALID);
289 osso_rpc_free_val (&retval);
290 if (result != OSSO_OK)
291 {
292 handle_status_change (plugin, STATUS_CONNECTION_FAILED,
293 "couldn't connect to charon");
294 return;
295 }
296
297 gchar *c_host, *c_cert, *c_user, *c_pass;
298
299 if (!get_password (plugin, &c_pass))
300 {
301 update_dialog_default (plugin);
302 return;
303 }
304
305 g_object_get (conn,
306 "host", &c_host,
307 "cert", &c_cert,
308 "user", &c_user,
309 NULL);
310
311 result = osso_rpc_run (priv->context,
312 OSSO_CHARON_SERVICE,
313 OSSO_CHARON_OBJECT,
314 OSSO_CHARON_IFACE,
315 "Connect",
316 &retval,
317 DBUS_TYPE_STRING, priv->current,
318 DBUS_TYPE_STRING, c_host,
319 DBUS_TYPE_STRING, c_cert,
320 DBUS_TYPE_STRING, c_user,
321 DBUS_TYPE_STRING, c_pass,
322 DBUS_TYPE_INVALID);
323
324 g_free (c_host);
325 g_free (c_cert);
326 g_free (c_user);
327 g_free (c_pass);
328
329 if (result != OSSO_OK || !retval.value.b)
330 {
331 handle_status_change (plugin, STATUS_CONNECTION_FAILED,
332 "initiation failed");
333 osso_rpc_free_val (&retval);
334 return;
335 }
336 osso_rpc_free_val (&retval);
337 }
338
339 static void
340 disconnect_clicked (HildonButton *button, StrongswanStatus *plugin)
341 {
342 osso_return_t result;
343 osso_rpc_t retval;
344 gchar *msg;
345 StrongswanStatusPrivate *priv = plugin->priv;
346
347 gtk_widget_set_sensitive (priv->box, FALSE);
348 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (priv->dialog), 1);
349
350 result = osso_rpc_run_system (priv->context,
351 OSSO_CHARON_SERVICE,
352 OSSO_CHARON_OBJECT,
353 OSSO_CHARON_IFACE,
354 "Disconnect",
355 &retval,
356 DBUS_TYPE_INVALID);
357
358 gtk_widget_set_sensitive (priv->box, TRUE);
359 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (priv->dialog), 0);
360
361 if (result == OSSO_OK)
362 {
363 msg = g_strdup_printf ("Successfully disconnected from %s",
364 priv->current);
365 }
366 else
367 {
368 msg = g_strdup_printf ("Failed to disconnect from %s", priv->current);
369 }
370 hildon_banner_show_information (NULL, NULL, msg);
371 g_free (msg);
372
373 priv->current = (g_free (priv->current), NULL);
374 priv->status = STATUS_DISCONNECTED;
375
376 update_status_menu (plugin);
377
378 gtk_dialog_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_OK);
379 }
380
381 static void
382 setup_dialog_disconnected (StrongswanStatus *plugin)
383 {
384 StrongswanStatusPrivate *priv = plugin->priv;
385
386 GtkWidget *vbox = GTK_DIALOG (priv->dialog)->vbox;
387 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
388 priv->box = hbox;
389 GtkWidget *button = hildon_picker_button_new (
390 HILDON_SIZE_FINGER_HEIGHT |
391 HILDON_SIZE_AUTO_WIDTH,
392 HILDON_BUTTON_ARRANGEMENT_HORIZONTAL);
393 hildon_button_set_title (HILDON_BUTTON (button), "Connection:");
394 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
395
396 GtkWidget *selector = hildon_touch_selector_new ();
397 priv->selector = selector;
398 GtkTreeModel *model = strongswan_connections_get_model (priv->conns);
399 hildon_touch_selector_append_text_column (
400 HILDON_TOUCH_SELECTOR (selector),
401 model,
402 TRUE);
403 hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (button),
404 HILDON_TOUCH_SELECTOR (selector));
405
406 button = hildon_button_new_with_text (
407 HILDON_SIZE_FINGER_HEIGHT |
408 HILDON_SIZE_AUTO_WIDTH,
409 HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
410 "Connect", NULL);
411 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
412 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
413 g_signal_connect (button, "clicked", G_CALLBACK (connect_clicked),
414 plugin);
415 }
416
417 static void
418 setup_dialog_connected (StrongswanStatus *plugin)
419 {
420 StrongswanStatusPrivate *priv = plugin->priv;
421
422 GtkWidget *vbox = GTK_DIALOG (priv->dialog)->vbox;
423 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
424 priv->box = hbox;
425 GtkWidget *button = hildon_button_new_with_text (
426 HILDON_SIZE_FINGER_HEIGHT |
427 HILDON_SIZE_AUTO_WIDTH,
428 HILDON_BUTTON_ARRANGEMENT_HORIZONTAL,
429 "Disconnect", priv->current);
430 hildon_button_set_style (HILDON_BUTTON (button),
431 HILDON_BUTTON_STYLE_PICKER);
432 g_signal_connect (button, "clicked", G_CALLBACK (disconnect_clicked),
433 plugin);
434 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
435 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
436 }
437
438 static void
439 button_clicked (HildonButton *button, StrongswanStatus *plugin)
440 {
441 StrongswanStatusPrivate *priv = plugin->priv;
442
443 priv->dialog = gtk_dialog_new ();
444 gtk_window_set_title (GTK_WINDOW (priv->dialog), "strongSwan VPN");
445 g_signal_connect (priv->dialog, "response",
446 G_CALLBACK (dialog_response), plugin);
447
448 switch (priv->status)
449 {
450 default:
451 case STATUS_DISCONNECTED:
452 setup_dialog_disconnected (plugin);
453 break;
454 case STATUS_CONNECTING:
455 setup_dialog_disconnected (plugin);
456 update_dialog_connecting (plugin);
457 break;
458 case STATUS_CONNECTED:
459 setup_dialog_connected (plugin);
460 break;
461 }
462
463 gtk_widget_show_all (priv->dialog);
464 }
465
466 static gint
467 dbus_req_handler(const gchar *interface, const gchar *method,
468 GArray *arguments, StrongswanStatus *plugin,
469 osso_rpc_t *retval)
470 {
471 if (!strcmp (method, "StatusChanged") && arguments->len == 1)
472 {
473 int status = 0;
474 osso_rpc_t *arg = &g_array_index(arguments, osso_rpc_t, 0);
475 if (arg->type == DBUS_TYPE_INT32)
476 {
477 status = arg->value.i;
478 }
479 handle_status_change (plugin, status, NULL);
480 }
481 return OSSO_OK;
482 }
483
484 static GdkPixbuf*
485 load_icon (GtkIconTheme *theme, const gchar *name, gint size)
486 {
487 GdkPixbuf *icon = NULL;
488 GdkPixbuf *loaded = gtk_icon_theme_load_icon (theme, name, size,
489 GTK_ICON_LOOKUP_NO_SVG, NULL);
490 if (loaded)
491 { /* so we don't have to listen for theme changes, we copy the icon */
492 icon = gdk_pixbuf_copy (loaded);
493 g_object_unref (loaded);
494 }
495 return icon;
496 }
497
498 static void
499 load_icons (StrongswanStatusPrivate *priv)
500 {
501 GtkIconTheme *theme = gtk_icon_theme_get_default ();
502 priv->icons.status_open = load_icon (theme, "strongswan_lock_open",
503 ICON_SIZE_STATUS);
504 priv->icons.status_close = load_icon (theme, "strongswan_lock_close",
505 ICON_SIZE_STATUS);
506 priv->icons.button_open = load_icon (theme, "strongswan_lock_open",
507 ICON_SIZE_BUTTON);
508 priv->icons.button_close = load_icon (theme, "strongswan_lock_close",
509 ICON_SIZE_BUTTON);
510 if (!priv->icons.status_open || !priv->icons.button_open)
511 {
512 hildon_banner_show_information (NULL, NULL, "failed to load icons");
513 }
514 }
515
516 static void
517 strongswan_status_init (StrongswanStatus *plugin)
518 {
519 StrongswanStatusPrivate *priv = STRONGSWAN_STATUS_GET_PRIVATE (plugin);
520 plugin->priv = priv;
521
522 priv->context = osso_initialize (OSSO_STATUS_SERVICE, "0.0.1", TRUE, NULL);
523 if (!priv->context)
524 {
525 return;
526 }
527 osso_return_t result;
528 result = osso_rpc_set_cb_f (priv->context,
529 OSSO_STATUS_SERVICE,
530 OSSO_STATUS_OBJECT,
531 OSSO_STATUS_IFACE,
532 (osso_rpc_cb_f*)dbus_req_handler,
533 plugin);
534 if (result != OSSO_OK)
535 {
536 return;
537 }
538
539 priv->conns = strongswan_connections_new ();
540
541 load_icons(priv);
542
543 hd_status_plugin_item_set_status_area_icon (HD_STATUS_PLUGIN_ITEM (plugin),
544 priv->icons.status_open);
545
546 GtkWidget *button = hildon_button_new_with_text (
547 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
548 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
549 "strongSwan VPN", "Not connected");
550 hildon_button_set_style (HILDON_BUTTON (button),
551 HILDON_BUTTON_STYLE_PICKER);
552 priv->button = button;
553 gtk_container_add (GTK_CONTAINER (plugin), button);
554
555 GtkWidget *image = gtk_image_new_from_pixbuf (priv->icons.button_open);
556 priv->image = image;
557 hildon_button_set_image (HILDON_BUTTON (button), image);
558
559 gtk_button_set_alignment (GTK_BUTTON (button), 0.0, 0.5);
560
561 g_signal_connect (button, "clicked", G_CALLBACK (button_clicked), plugin);
562
563 gtk_widget_show_all (GTK_WIDGET (plugin));
564 }
565
566 #define UNREF_IF(obj) do { \
567 if (obj) { obj = (g_object_unref (obj), NULL); } \
568 } while(0)
569
570 static void
571 strongswan_status_dispose (GObject *object)
572 {
573 StrongswanStatusPrivate *priv = STRONGSWAN_STATUS (object)->priv;
574 if (priv->context)
575 {
576 osso_rpc_unset_cb_f (priv->context,
577 OSSO_STATUS_SERVICE,
578 OSSO_STATUS_OBJECT,
579 OSSO_STATUS_IFACE,
580 (osso_rpc_cb_f*)dbus_req_handler,
581 STRONGSWAN_STATUS (object));
582 osso_deinitialize (priv->context);
583 priv->context = NULL;
584 }
585 UNREF_IF(priv->conns);
586 UNREF_IF(priv->icons.status_open);
587 UNREF_IF(priv->icons.status_close);
588 UNREF_IF(priv->icons.button_open);
589 UNREF_IF(priv->icons.button_close);
590 G_OBJECT_CLASS (strongswan_status_parent_class)->dispose (object);
591 }
592
593 static void
594 strongswan_status_finalize (GObject *object)
595 {
596 StrongswanStatusPrivate *priv = STRONGSWAN_STATUS (object)->priv;
597 priv->current = (g_free (priv->current), NULL);
598 G_OBJECT_CLASS (strongswan_status_parent_class)->finalize (object);
599 }
600
601 static void
602 strongswan_status_class_finalize (StrongswanStatusClass *klass)
603 {
604 }
605
606 static void
607 strongswan_status_class_init (StrongswanStatusClass *klass)
608 {
609 GObjectClass *object_class = G_OBJECT_CLASS (klass);
610
611 object_class->dispose = strongswan_status_dispose;
612 object_class->finalize = strongswan_status_finalize;
613
614 g_type_class_add_private (klass, sizeof (StrongswanStatusPrivate));
615 }