2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2010 Intel Corporation.
6 * Authors: Jussi Kukkonen <jku@linux.intel.com>
7 * David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
35 #include <gconf/gconf-client.h>
39 #include "auth-dlg-settings.h"
41 #include "openconnect-internal.h"
43 #include <openssl/bio.h>
44 #include <openssl/ui.h>
46 static GConfClient *gcl;
47 static char *config_path;
49 static char *last_message;
51 static char *lasthost;
53 typedef struct vpnhost {
62 enum certificate_response{
71 struct gconf_key *next;
74 typedef struct auth_ui_data {
76 struct openconnect_info *vpninfo;
77 struct gconf_key *success_keys;
80 GtkWidget *connect_button;
81 GtkWidget *no_form_label;
82 GtkWidget *getting_form_label;
84 GtkWidget *cancel_button;
85 GtkWidget *login_button;
91 gboolean cancelled; /* fully cancel the whole challenge-response series */
92 gboolean getting_cookie;
95 GQueue *form_entries; /* modified from worker thread */
98 GCond *form_retval_changed;
101 GCond *form_shown_changed;
104 GCond *cert_response_changed;
105 enum certificate_response cert_response;
109 AUTH_DIALOG_RESPONSE_LOGIN = 1,
110 AUTH_DIALOG_RESPONSE_CANCEL,
111 } auth_dialog_response;
115 /* this is here because ssl ui (*opener) does not have a userdata pointer... */
116 static auth_ui_data *ui_data;
118 static void connect_host(auth_ui_data *ui_data);
120 static void container_child_remove(GtkWidget *widget, gpointer data)
122 GtkContainer *container = GTK_CONTAINER(data);
124 gtk_container_remove(container, widget);
127 static void ssl_box_add_error(auth_ui_data *ui_data, const char *msg)
129 GtkWidget *hbox, *text, *image;
132 hbox = gtk_hbox_new(FALSE, 8);
133 gtk_box_pack_start(GTK_BOX(ui_data->ssl_box), hbox, FALSE, FALSE, 0);
135 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR,
136 GTK_ICON_SIZE_DIALOG);
137 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
139 text = gtk_label_new(msg);
140 gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
141 gtk_window_get_size(GTK_WINDOW(ui_data->dialog), &width, NULL);
142 /* FIXME: this is not very nice -- can't make the window thinner after this */
143 gtk_widget_set_size_request(text, width - 100, -1);
144 gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 0);
147 static void ssl_box_add_info(auth_ui_data *ui_data, const char *msg)
152 text = gtk_label_new(msg);
153 gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
154 gtk_window_get_size(GTK_WINDOW(ui_data->dialog), &width, NULL);
155 /* FIXME: this is not very nice -- can't make the window thinner after this */
156 gtk_widget_set_size_request(text, width - 40, -1);
157 gtk_box_pack_start(GTK_BOX(ui_data->ssl_box), text, FALSE, FALSE, 0);
160 static void ssl_box_clear(auth_ui_data *ui_data)
162 gtk_widget_hide(ui_data->no_form_label);
163 gtk_widget_hide(ui_data->getting_form_label);
164 gtk_container_foreach(GTK_CONTAINER(ui_data->ssl_box),
165 container_child_remove, ui_data->ssl_box);
166 gtk_widget_set_sensitive (ui_data->login_button, FALSE);
167 gtk_widget_set_sensitive (ui_data->cancel_button, FALSE);
170 typedef struct ui_fragment_data {
172 auth_ui_data *ui_data;
174 struct oc_form_opt *opt;
179 static void entry_activate_cb(GtkWidget *widget, auth_ui_data *ui_data)
181 gtk_dialog_response(GTK_DIALOG(ui_data->dialog), AUTH_DIALOG_RESPONSE_LOGIN);
184 static void do_check_visibility(ui_fragment_data *data, gboolean *visible)
191 min_len = UI_get_result_minsize(data->uis);
193 if (min_len && (!data->entry_text || strlen(data->entry_text) < min_len))
197 static void evaluate_login_visibility(auth_ui_data *ui_data)
199 gboolean visible = TRUE;
200 g_queue_foreach(ui_data->form_entries, (GFunc)do_check_visibility,
203 gtk_widget_set_sensitive (ui_data->login_button, visible);
206 static void entry_changed(GtkEntry *entry, ui_fragment_data *data)
208 g_free (data->entry_text);
209 data->entry_text = g_strdup(gtk_entry_get_text(entry));
210 evaluate_login_visibility(data->ui_data);
213 static void do_override_label(ui_fragment_data *data, struct oc_choice *choice)
215 const char *new_label = data->opt->label;
220 if (choice->override_name && !strcmp(choice->override_name, data->opt->name))
221 new_label = choice->override_label;
223 gtk_label_set_text(GTK_LABEL(data->widget), new_label);
226 static void combo_changed(GtkComboBox *combo, ui_fragment_data *data)
228 struct oc_form_opt_select *sopt = (void *)data->opt;
229 int entry = gtk_combo_box_get_active(combo);
233 data->entry_text = sopt->choices[entry].name;
235 g_queue_foreach(data->ui_data->form_entries, (GFunc)do_override_label,
236 &sopt->choices[entry]);
239 static gboolean ui_write_error (ui_fragment_data *data)
241 ssl_box_add_error(data->ui_data, UI_get0_output_string(data->uis));
243 g_slice_free (ui_fragment_data, data);
248 static gboolean ui_write_info (ui_fragment_data *data)
250 ssl_box_add_info(data->ui_data, UI_get0_output_string(data->uis));
252 g_slice_free (ui_fragment_data, data);
257 static gboolean ui_write_prompt (ui_fragment_data *data)
259 GtkWidget *hbox, *text, *entry;
264 label = UI_get0_output_string(data->uis);
265 visible = UI_get_input_flags(data->uis) & UI_INPUT_FLAG_ECHO;
267 label = data->opt->label;
268 visible = (data->opt->type == OC_FORM_OPT_TEXT);
271 hbox = gtk_hbox_new(FALSE, 0);
272 gtk_box_pack_start(GTK_BOX(data->ui_data->ssl_box), hbox, FALSE, FALSE, 0);
274 text = gtk_label_new(label);
275 gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 0);
278 entry = gtk_entry_new();
279 gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
281 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
282 if (data->entry_text)
283 gtk_entry_set_text(GTK_ENTRY(entry), data->entry_text);
284 if (!data->entry_text && !data->ui_data->form_grabbed) {
285 data->ui_data->form_grabbed = 1;
286 gtk_widget_grab_focus (entry);
288 g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(entry_changed), data);
289 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(entry_activate_cb), ui_data);
291 /* data is freed in ui_flush in worker thread */
296 static gboolean ui_add_select (ui_fragment_data *data)
298 GtkWidget *hbox, *text, *combo;
299 struct oc_form_opt_select *sopt = (void *)data->opt;
302 hbox = gtk_hbox_new(FALSE, 0);
303 gtk_box_pack_start(GTK_BOX(data->ui_data->ssl_box), hbox, FALSE, FALSE, 0);
305 text = gtk_label_new(data->opt->label);
306 gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 0);
308 combo = gtk_combo_box_new_text();
309 gtk_box_pack_end(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
310 for (i = 0; i < sopt->nr_choices; i++) {
311 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), sopt->choices[i].label);
312 if (data->entry_text &&
313 !strcmp(data->entry_text, sopt->choices[i].name)) {
314 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
315 g_free(data->entry_text);
316 data->entry_text = sopt->choices[i].name;
319 if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) {
320 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
321 data->entry_text = sopt->choices[0].name;
324 if (g_queue_peek_tail(ui_data->form_entries) == data)
325 gtk_widget_grab_focus (combo);
326 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(combo_changed), data);
327 /* Hook up the 'show' signal to ensure that we override prompts on
328 UI elements which may be coming later. */
329 g_signal_connect(G_OBJECT(combo), "show", G_CALLBACK(combo_changed), data);
331 /* data is freed in ui_flush in worker thread */
336 static gboolean ui_show (auth_ui_data *ui_data)
338 gtk_widget_hide (ui_data->getting_form_label);
339 gtk_widget_show_all (ui_data->ssl_box);
340 gtk_widget_set_sensitive (ui_data->cancel_button, TRUE);
341 g_mutex_lock (ui_data->form_mutex);
342 evaluate_login_visibility(ui_data);
343 ui_data->form_shown = TRUE;
344 g_cond_signal (ui_data->form_shown_changed);
345 g_mutex_unlock (ui_data->form_mutex);
350 /* runs in worker thread */
351 static int ui_open(UI *ui)
353 UI_add_user_data(ui, ui_data);
358 /* runs in worker thread */
359 static int ui_write(UI *ui, UI_STRING *uis)
361 auth_ui_data *ui_data;
362 ui_fragment_data *data;
364 ui_data = UI_get0_user_data(ui);
366 /* return if a new host has been selected */
367 if (ui_data->cancelled) {
371 data = g_slice_new0 (ui_fragment_data);
372 data->ui_data = ui_data;
375 switch(UI_get_string_type(uis)) {
377 g_idle_add ((GSourceFunc)ui_write_error, data);
381 g_idle_add ((GSourceFunc)ui_write_info, data);
386 g_mutex_lock (ui_data->form_mutex);
387 g_queue_push_head(ui_data->form_entries, data);
388 g_mutex_unlock (ui_data->form_mutex);
390 g_idle_add ((GSourceFunc)ui_write_prompt, data);
397 g_slice_free (ui_fragment_data, data);
402 /* runs in worker thread */
403 static int ui_flush(UI* ui)
405 auth_ui_data *ui_data;
408 ui_data = UI_get0_user_data(ui);
410 g_idle_add((GSourceFunc)ui_show, ui_data);
411 g_mutex_lock(ui_data->form_mutex);
412 /* wait for ui to show */
413 while (!ui_data->form_shown) {
414 g_cond_wait(ui_data->form_shown_changed, ui_data->form_mutex);
416 ui_data->form_shown = FALSE;
418 if (!ui_data->cancelled) {
419 /* wait for form submission or cancel */
420 while (!ui_data->form_retval) {
421 g_cond_wait(ui_data->form_retval_changed, ui_data->form_mutex);
423 response = GPOINTER_TO_INT (ui_data->form_retval);
424 ui_data->form_retval = NULL;
426 response = AUTH_DIALOG_RESPONSE_CANCEL;
428 /* set entry results and free temporary data structures */
429 while (!g_queue_is_empty (ui_data->form_entries)) {
430 ui_fragment_data *data;
431 data = g_queue_pop_tail (ui_data->form_entries);
432 if (data->entry_text) {
433 UI_set_result(ui, data->uis, data->entry_text);
435 g_slice_free (ui_fragment_data, data);
437 ui_data->form_grabbed = 0;
438 g_mutex_unlock(ui_data->form_mutex);
443 return (response == AUTH_DIALOG_RESPONSE_LOGIN ? 1 : -1);
446 /* runs in worker thread */
447 static int ui_close(UI *ui)
452 int init_openssl_ui(void)
454 UI_METHOD *ui_method = UI_create_method("OpenConnect VPN UI (gtk)");
456 UI_method_set_opener(ui_method, ui_open);
457 UI_method_set_flusher(ui_method, ui_flush);
458 UI_method_set_writer(ui_method, ui_write);
459 UI_method_set_closer(ui_method, ui_close);
461 UI_set_default_method(ui_method);
465 void remember_gconf_key(auth_ui_data *ui_data, char *key, char *value)
467 struct gconf_key *k = g_malloc(sizeof(*k));
472 k->next = ui_data->success_keys;
476 ui_data->success_keys = k;
478 if (!strcmp(k->next->key, key)) {
479 struct gconf_key *old = k->next;
490 char *find_form_answer(struct oc_auth_form *form, struct oc_form_opt *opt)
493 key = g_strdup_printf("%s/vpn/form:%s:%s", config_path,
494 form->auth_id, opt->name);
495 result = gconf_client_get_string(gcl, key, NULL);
500 /* This part for processing forms from openconnect directly, rather than
501 through the SSL UI abstraction (which doesn't allow 'select' options) */
503 static gboolean ui_form (struct oc_auth_form *form)
505 struct oc_form_opt *opt;
507 ssl_box_clear(ui_data);
509 g_mutex_lock(ui_data->form_mutex);
510 while (!g_queue_is_empty (ui_data->form_entries)) {
511 ui_fragment_data *data;
512 data = g_queue_pop_tail (ui_data->form_entries);
513 g_slice_free (ui_fragment_data, data);
515 g_mutex_unlock(ui_data->form_mutex);
518 ssl_box_add_info(ui_data, form->banner);
520 ssl_box_add_error(ui_data, form->error);
522 ssl_box_add_info(ui_data, form->message);
524 for (opt = form->opts; opt; opt = opt->next) {
525 ui_fragment_data *data;
527 if (opt->type == OC_FORM_OPT_HIDDEN)
530 data = g_slice_new0 (ui_fragment_data);
531 data->ui_data = ui_data;
534 if (opt->type == OC_FORM_OPT_PASSWORD ||
535 opt->type == OC_FORM_OPT_TEXT) {
536 g_mutex_lock (ui_data->form_mutex);
537 g_queue_push_head(ui_data->form_entries, data);
538 g_mutex_unlock (ui_data->form_mutex);
539 if (opt->type != OC_FORM_OPT_PASSWORD)
540 data->entry_text = find_form_answer(form, opt);
542 ui_write_prompt(data);
543 } else if (opt->type == OC_FORM_OPT_SELECT) {
544 g_mutex_lock (ui_data->form_mutex);
545 g_queue_push_head(ui_data->form_entries, data);
546 g_mutex_unlock (ui_data->form_mutex);
547 data->entry_text = find_form_answer(form, opt);
551 g_slice_free (ui_fragment_data, data);
554 return ui_show(ui_data);
557 int nm_process_auth_form (struct openconnect_info *vpninfo,
558 struct oc_auth_form *form)
562 g_idle_add((GSourceFunc)ui_form, form);
564 g_mutex_lock(ui_data->form_mutex);
565 /* wait for ui to show */
566 while (!ui_data->form_shown) {
567 g_cond_wait(ui_data->form_shown_changed, ui_data->form_mutex);
569 ui_data->form_shown = FALSE;
571 if (!ui_data->cancelled) {
572 /* wait for form submission or cancel */
573 while (!ui_data->form_retval) {
574 g_cond_wait(ui_data->form_retval_changed, ui_data->form_mutex);
576 response = GPOINTER_TO_INT (ui_data->form_retval);
577 ui_data->form_retval = NULL;
579 response = AUTH_DIALOG_RESPONSE_CANCEL;
581 if (response == AUTH_DIALOG_RESPONSE_LOGIN) {
582 /* set entry results and free temporary data structures */
583 while (!g_queue_is_empty (ui_data->form_entries)) {
584 ui_fragment_data *data;
585 data = g_queue_pop_tail (ui_data->form_entries);
586 if (data->entry_text) {
587 data->opt->value = data->entry_text;
589 if (data->opt->type == OC_FORM_OPT_TEXT ||
590 data->opt->type == OC_FORM_OPT_SELECT) {
592 keyname = g_strdup_printf("form:%s:%s", form->auth_id, data->opt->name);
593 remember_gconf_key(ui_data, keyname, strdup(data->entry_text));
596 g_slice_free (ui_fragment_data, data);
601 g_mutex_unlock(ui_data->form_mutex);
606 return (response == AUTH_DIALOG_RESPONSE_LOGIN ? 0 : 1);
610 static char* get_title(const char *vpn_name)
613 return g_strdup_printf("Connect to VPN '%s'", vpn_name);
615 return g_strdup("Connect to VPN");
618 typedef struct cert_data {
619 auth_ui_data *ui_data;
625 static gboolean user_validate_cert(cert_data *data)
627 BIO *bp = BIO_new(BIO_s_mem());
631 GtkWidget *dlg, *text, *scroll;
632 GtkTextBuffer *buffer;
635 /* There are probably better ways to do this -- getting individual
636 elements of the cert info and formatting it nicely in the dialog
637 box. But this will do for now... */
638 X509_print_ex(bp, data->peer_cert, 0, 0);
639 BIO_write(bp, &zero, 1);
640 BIO_get_mem_ptr(bp, &certinfo);
642 title = get_title(data->ui_data->vpn_name);
643 msg = g_strdup_printf("Certificate from VPN server \"%s\" failed verification.\n"
644 "Reason: %s\nDo you want to accept it?",
645 data->ui_data->vpninfo->hostname, data->reason);
647 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
648 GTK_BUTTONS_OK_CANCEL,
650 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dlg), FALSE);
651 gtk_window_set_skip_pager_hint(GTK_WINDOW(dlg), FALSE);
652 gtk_window_set_title(GTK_WINDOW(dlg), title);
653 gtk_window_set_default_size(GTK_WINDOW(dlg), 550, 600);
654 gtk_window_set_resizable(GTK_WINDOW(dlg), TRUE);
655 gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_CANCEL);
660 scroll = gtk_scrolled_window_new(NULL, NULL);
661 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), scroll, TRUE, TRUE, 0);
662 gtk_widget_show(scroll);
664 text = gtk_text_view_new();
665 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
666 gtk_text_buffer_set_text(buffer, certinfo->data, -1);
667 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), 0);
668 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
669 gtk_container_add(GTK_CONTAINER(scroll), text);
670 gtk_widget_show(text);
672 result = gtk_dialog_run(GTK_DIALOG(dlg));
675 gtk_widget_destroy(dlg);
677 g_mutex_lock (ui_data->form_mutex);
678 if (result == GTK_RESPONSE_OK)
679 data->ui_data->cert_response = CERT_ACCEPTED;
681 data->ui_data->cert_response = CERT_DENIED;
682 g_cond_signal (ui_data->cert_response_changed);
683 g_mutex_unlock (ui_data->form_mutex);
688 /* runs in worker thread */
689 static int validate_peer_cert(struct openconnect_info *vpninfo,
690 X509 *peer_cert, const char *reason)
692 char fingerprint[EVP_MAX_MD_SIZE * 2 + 1];
698 ret = get_cert_sha1_fingerprint(vpninfo, peer_cert, fingerprint);
702 key = g_strdup_printf("%s/vpn/%s", config_path, "certsigs");
703 certs_data = gconf_client_get_string(gcl, key, NULL);
705 char **certs = g_strsplit_set(certs_data, "\t", 0);
709 if (!strcmp(*this, fingerprint)) {
718 data = g_slice_new(cert_data);
719 data->ui_data = ui_data; /* FIXME uses global */
720 data->peer_cert = peer_cert;
721 data->reason = reason;
723 g_mutex_lock(ui_data->form_mutex);
725 ui_data->cert_response = CERT_USER_NOT_READY;
726 g_idle_add((GSourceFunc)user_validate_cert, data);
728 /* wait for user to accept or cancel */
729 while (ui_data->cert_response == CERT_USER_NOT_READY) {
730 g_cond_wait(ui_data->cert_response_changed, ui_data->form_mutex);
732 if (ui_data->cert_response == CERT_ACCEPTED) {
734 char *new = g_strdup_printf("%s\t%s", certs_data, fingerprint);
735 gconf_client_set_string(gcl, key, new, NULL);
738 gconf_client_set_string(gcl, key, fingerprint, NULL);
744 g_mutex_unlock (ui_data->form_mutex);
746 g_slice_free(cert_data, data);
754 static char *get_config_path(GConfClient *gcl, const char *vpn_uuid)
756 GSList *connections, *this;
758 char *config_path = NULL;
760 connections = gconf_client_all_dirs(gcl,
761 "/system/networking/connections",
764 for (this = connections; this; this = this->next) {
765 const char *path = (const char *) this->data;
767 key = g_strdup_printf("%s/connection/type", path);
768 val = gconf_client_get_string(gcl, key, NULL);
771 if (!val || strcmp(val, "vpn")) {
777 key = g_strdup_printf("%s/connection/uuid", path);
778 val = gconf_client_get_string(gcl, key, NULL);
781 if (!val || strcmp(val, vpn_uuid)) {
787 config_path = g_strdup(path);
790 g_slist_foreach(connections, (GFunc)g_free, NULL);
791 g_slist_free(connections);
796 static char *get_gconf_setting(GConfClient *gcl, char *config_path,
800 char *key = g_strdup_printf("%s/vpn/%s", config_path, setting);
801 result = gconf_client_get_string(gcl, key, NULL);
806 static int get_gconf_autoconnect(GConfClient *gcl, char *config_path)
808 char *autoconnect = get_gconf_setting(gcl, config_path, "autoconnect");
812 if (!strcmp(autoconnect, "yes"))
819 static int parse_xmlconfig(char *xmlconfig)
822 xmlNode *xml_node, *xml_node2;
823 struct vpnhost *newhost, **list_end;
825 list_end = &vpnhosts->next;
826 /* gateway may be there already */
828 list_end = &(*list_end)->next;
831 xml_doc = xmlReadMemory(xmlconfig, strlen(xmlconfig), "noname.xml", NULL, 0);
833 xml_node = xmlDocGetRootElement(xml_doc);
834 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
835 if (xml_node->type == XML_ELEMENT_NODE &&
836 !strcmp((char *)xml_node->name, "ServerList")) {
838 for (xml_node = xml_node->children; xml_node;
839 xml_node = xml_node->next) {
841 if (xml_node->type == XML_ELEMENT_NODE &&
842 !strcmp((char *)xml_node->name, "HostEntry")) {
845 newhost = malloc(sizeof(*newhost));
849 memset(newhost, 0, sizeof(*newhost));
850 for (xml_node2 = xml_node->children;
851 match >= 0 && xml_node2; xml_node2 = xml_node2->next) {
853 if (xml_node2->type != XML_ELEMENT_NODE)
856 if (!strcmp((char *)xml_node2->name, "HostName")) {
857 char *content = (char *)xmlNodeGetContent(xml_node2);
858 newhost->hostname = content;
859 } else if (!strcmp((char *)xml_node2->name, "HostAddress")) {
860 char *content = (char *)xmlNodeGetContent(xml_node2);
861 newhost->hostaddress = content;
862 } else if (!strcmp((char *)xml_node2->name, "UserGroup")) {
863 char *content = (char *)xmlNodeGetContent(xml_node2);
864 newhost->usergroup = content;
867 if (newhost->hostname && newhost->hostaddress) {
869 list_end = &newhost->next;
871 if (!strcasecmp(newhost->hostaddress, vpnhosts->hostaddress) &&
872 !strcasecmp(newhost->usergroup ?: "", vpnhosts->usergroup ?: "")) {
873 /* Remove originally configured host if it's in the list */
874 struct vpnhost *tmp = vpnhosts->next;
890 static int get_config(char *vpn_uuid, struct openconnect_info *vpninfo)
898 char *pem_passphrase_fsid;
900 gcl = gconf_client_get_default();
901 config_path = get_config_path(gcl, vpn_uuid);
906 hostname = get_gconf_setting(gcl, config_path,
907 NM_OPENCONNECT_KEY_GATEWAY);
909 fprintf(stderr, "No gateway configured\n");
913 /* add gateway to host list */
914 vpnhosts = malloc(sizeof(*vpnhosts));
917 vpnhosts->hostname = g_strdup(hostname);
918 group = strchr(hostname, '/');
921 vpnhosts->usergroup = g_strdup(group);
923 vpnhosts->usergroup = NULL;
924 vpnhosts->hostaddress = hostname;
925 vpnhosts->next = NULL;
928 /* DEBUG add another copy of gateway to host list */
930 tmphost = malloc(sizeof(tmphost));
933 tmphost->hostname = g_strdup("VPN Gateway 2");
934 tmphost->hostaddress = hostname;
935 tmphost->usergroup = NULL;
936 tmphost->next = NULL;
937 vpnhosts->next = tmphost;
939 lasthost = get_gconf_setting(gcl, config_path, "lasthost");
941 xmlconfig = get_gconf_setting(gcl, config_path, NM_OPENCONNECT_KEY_XMLCONFIG);
943 unsigned char sha1[SHA_DIGEST_LENGTH];
948 EVP_Digest(xmlconfig, strlen(xmlconfig), sha1, NULL, EVP_sha1(), NULL);
949 EVP_MD_CTX_cleanup(&c);
951 for (i = 0; i < SHA_DIGEST_LENGTH; i++)
952 sprintf(&vpninfo->xmlsha1[i*2], "%02x", sha1[i]);
954 parse_xmlconfig(xmlconfig);
958 vpninfo->cafile = get_gconf_setting(gcl, config_path, NM_OPENCONNECT_KEY_CACERT);
960 csd = get_gconf_setting(gcl, config_path, "enable_csd_trojan");
961 if (csd && !strcmp(csd, "yes")) {
962 /* We're not running as root; we can't setuid(). */
963 vpninfo->uid_csd = getuid();
964 vpninfo->uid_csd_given = 2;
966 csd_wrapper = get_gconf_setting(gcl, config_path, "csd_wrapper");
967 if (csd_wrapper && csd_wrapper[0] )
968 vpninfo->csd_wrapper = csd_wrapper;
974 proxy = get_gconf_setting(gcl, config_path, "proxy");
975 if (proxy && proxy[0] && openconnect_set_http_proxy(vpninfo, proxy))
978 vpninfo->cert = get_gconf_setting(gcl, config_path, NM_OPENCONNECT_KEY_USERCERT);
979 vpninfo->sslkey = get_gconf_setting(gcl, config_path, NM_OPENCONNECT_KEY_PRIVKEY);
980 if (!vpninfo->sslkey)
981 vpninfo->sslkey = vpninfo->cert;
983 pem_passphrase_fsid = get_gconf_setting(gcl, config_path, "pem_passphrase_fsid");
984 if (pem_passphrase_fsid && vpninfo->sslkey && !strcmp(pem_passphrase_fsid, "yes"))
985 openconnect_passphrase_from_fsid(vpninfo);
986 g_free(pem_passphrase_fsid);
991 static void populate_vpnhost_combo(auth_ui_data *ui_data)
993 struct vpnhost *host;
995 GtkComboBox *combo = GTK_COMBO_BOX(ui_data->combo);
997 for (host = vpnhosts; host; host = host->next) {
998 gtk_combo_box_append_text(combo, host->hostname);
1001 (lasthost && !strcmp(host->hostname, lasthost)))
1002 gtk_combo_box_set_active(combo, i);
1008 int write_new_config(struct openconnect_info *vpninfo, char *buf, int buflen)
1010 char *key = g_strdup_printf("%s/vpn/%s", config_path,
1011 NM_OPENCONNECT_KEY_XMLCONFIG);
1012 gconf_client_set_string(gcl, key, buf, NULL);
1016 static void autocon_toggled(GtkWidget *widget)
1018 int enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1019 char *key = g_strdup_printf("%s/vpn/autoconnect", config_path);
1021 gconf_client_set_string(gcl, key, enabled ? "yes" : "no", NULL);
1024 static void scroll_log(GtkTextBuffer *log, GtkTextView *view)
1028 g_return_if_fail(GTK_IS_TEXT_VIEW(view));
1030 mark = gtk_text_buffer_get_insert(log);
1031 gtk_text_view_scroll_to_mark(view, mark, 0.0, FALSE, 0.0, 0.0);
1034 /* NOTE: write_progress_real() will free the given string */
1035 static gboolean write_progress_real(char *message)
1039 g_return_val_if_fail(message, FALSE);
1041 gtk_text_buffer_get_end_iter(ui_data->log, &iter);
1042 gtk_text_buffer_insert(ui_data->log, &iter, message, -1);
1049 /* runs in worker thread */
1050 void write_progress(struct openconnect_info *info, int level, const char *fmt, ...)
1056 g_free(last_message);
1057 last_message = NULL;
1060 va_start(args, fmt);
1061 msg = g_strdup_vprintf(fmt, args);
1064 if (level <= PRG_DEBUG) {
1065 g_idle_add((GSourceFunc)write_progress_real, g_strdup(msg));
1068 if (level <= PRG_ERR) {
1075 static void print_peer_cert(struct openconnect_info *vpninfo)
1077 char fingerprint[EVP_MAX_MD_SIZE * 2 + 1];
1078 X509 *cert = SSL_get_peer_certificate(vpninfo->https_ssl);
1080 if (cert && !get_cert_sha1_fingerprint(vpninfo, cert, fingerprint))
1081 printf("gwcert\n%s\n", fingerprint);
1084 static gboolean cookie_obtained(auth_ui_data *ui_data)
1086 ui_data->getting_cookie = FALSE;
1087 gtk_widget_hide (ui_data->getting_form_label);
1089 if (ui_data->cancelled) {
1090 /* user has chosen a new host, start from beginning */
1091 while (ui_data->success_keys) {
1092 struct gconf_key *k = ui_data->success_keys;
1094 ui_data->success_keys = k->next;
1099 connect_host(ui_data);
1103 if (ui_data->cookie_retval < 0) {
1104 /* error while getting cookie */
1106 ssl_box_add_error(ui_data, last_message);
1107 gtk_widget_show_all(ui_data->ssl_box);
1108 gtk_widget_set_sensitive(ui_data->cancel_button, TRUE);
1110 ui_data->retval = 1;
1111 } else if (!ui_data->cookie_retval) {
1113 while (ui_data->success_keys) {
1114 struct gconf_key *k = ui_data->success_keys;
1115 char *key = g_strdup_printf("%s/vpn/%s", config_path, k->key);
1117 gconf_client_set_string(gcl, key, k->value, NULL);
1120 ui_data->success_keys = k->next;
1126 printf("%s\n%s:%d\n", NM_OPENCONNECT_KEY_GATEWAY,
1127 ui_data->vpninfo->hostname, ui_data->vpninfo->port);
1128 printf("%s\n%s\n", NM_OPENCONNECT_KEY_COOKIE,
1129 ui_data->vpninfo->cookie);
1130 print_peer_cert(ui_data->vpninfo);
1131 memset((void *)ui_data->vpninfo->cookie, 0, strlen(ui_data->vpninfo->cookie));
1134 ui_data->retval = 0;
1138 /* no cookie; user cancellation */
1139 gtk_widget_show (ui_data->no_form_label);
1140 ui_data->retval = 1;
1143 while (ui_data->success_keys) {
1144 struct gconf_key *k = ui_data->success_keys;
1146 ui_data->success_keys = k->next;
1155 gpointer obtain_cookie (auth_ui_data *ui_data)
1159 ret = openconnect_obtain_cookie(ui_data->vpninfo);
1161 ui_data->cookie_retval = ret;
1162 g_idle_add ((GSourceFunc)cookie_obtained, ui_data);
1167 static void connect_host(auth_ui_data *ui_data)
1174 ui_data->cancelled = FALSE;
1175 ui_data->getting_cookie = TRUE;
1177 g_mutex_lock (ui_data->form_mutex);
1178 ui_data->form_retval = NULL;
1179 g_mutex_unlock (ui_data->form_mutex);
1181 ssl_box_clear(ui_data);
1182 gtk_widget_show(ui_data->getting_form_label);
1184 /* reset ssl context.
1185 * TODO: this is probably not the way to go... */
1186 if (ui_data->vpninfo->https_ssl) {
1187 free(ui_data->vpninfo->peer_addr);
1188 ui_data->vpninfo->peer_addr = NULL;
1189 openconnect_close_https(ui_data->vpninfo);
1191 if (ui_data->vpninfo->https_ctx) {
1192 SSL_CTX_free(ui_data->vpninfo->https_ctx);
1193 ui_data->vpninfo->https_ctx = NULL;
1196 host_nr = gtk_combo_box_get_active(GTK_COMBO_BOX(ui_data->combo));
1198 for (i = 0; i < host_nr; i++)
1201 if (internal_parse_url(host->hostaddress, NULL,
1202 &ui_data->vpninfo->hostname, &ui_data->vpninfo->port,
1203 &ui_data->vpninfo->urlpath, 443)) {
1204 fprintf(stderr, "Failed to parse server URL '%s'\n",
1206 ui_data->vpninfo->hostname = g_strdup(host->hostaddress);
1209 if (!ui_data->vpninfo->urlpath && host->usergroup)
1210 ui_data->vpninfo->urlpath = g_strdup(host->usergroup);
1212 remember_gconf_key(ui_data, g_strdup("lasthost"), g_strdup(host->hostname));
1214 thread = g_thread_create((GThreadFunc)obtain_cookie, ui_data,
1219 static void queue_connect_host(auth_ui_data *ui_data)
1221 ssl_box_clear(ui_data);
1222 gtk_widget_show(ui_data->getting_form_label);
1223 gtk_widget_hide(ui_data->no_form_label);
1225 if (!ui_data->getting_cookie) {
1226 connect_host(ui_data);
1227 } else if (!ui_data->cancelled) {
1228 /* set state to cancelled. Current challenge-response-
1229 * conversation will not be shown to user, and cookie_obtained()
1230 * will start a new one conversation */
1231 ui_data->cancelled = TRUE;
1232 gtk_dialog_response(GTK_DIALOG(ui_data->dialog), AUTH_DIALOG_RESPONSE_CANCEL);
1236 static void dialog_response (GtkDialog *dialog, int response, auth_ui_data *ui_data)
1239 case AUTH_DIALOG_RESPONSE_CANCEL:
1240 case AUTH_DIALOG_RESPONSE_LOGIN:
1241 ssl_box_clear(ui_data);
1242 if (ui_data->getting_cookie)
1243 gtk_widget_show (ui_data->getting_form_label);
1244 g_mutex_lock (ui_data->form_mutex);
1245 ui_data->form_retval = GINT_TO_POINTER(response);
1246 g_cond_signal (ui_data->form_retval_changed);
1247 g_mutex_unlock (ui_data->form_mutex);
1249 case GTK_RESPONSE_CLOSE:
1257 static void cancel_clicked (GtkButton *btn, auth_ui_data *ui_data)
1259 gtk_dialog_response (GTK_DIALOG(ui_data->dialog), AUTH_DIALOG_RESPONSE_CANCEL);
1262 static void login_clicked (GtkButton *btn, auth_ui_data *ui_data)
1264 gtk_dialog_response (GTK_DIALOG(ui_data->dialog), AUTH_DIALOG_RESPONSE_LOGIN);
1267 static void build_main_dialog(auth_ui_data *ui_data)
1270 GtkWidget *vbox, *hbox, *label, *frame, *image, *frame_box;
1271 GtkWidget *exp, *scrolled, *view, *autocon;
1273 gtk_window_set_default_icon_name(GTK_STOCK_DIALOG_AUTHENTICATION);
1275 title = get_title(ui_data->vpn_name);
1276 ui_data->dialog = gtk_dialog_new_with_buttons(title, NULL, GTK_DIALOG_MODAL,
1277 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1279 g_signal_connect (ui_data->dialog, "response", G_CALLBACK(dialog_response), ui_data);
1280 gtk_window_set_default_size(GTK_WINDOW(ui_data->dialog), 350, 300);
1281 g_signal_connect_swapped(ui_data->dialog, "destroy",
1282 G_CALLBACK(gtk_main_quit), NULL);
1285 vbox = gtk_vbox_new(FALSE, 8);
1286 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(ui_data->dialog)->vbox), vbox, TRUE, TRUE, 0);
1287 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1288 gtk_widget_show(vbox);
1290 hbox = gtk_hbox_new(FALSE, 4);
1291 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1292 gtk_widget_show(hbox);
1294 label = gtk_label_new("VPN host");
1295 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1296 gtk_widget_show(label);
1298 ui_data->combo = gtk_combo_box_new_text();
1299 populate_vpnhost_combo(ui_data);
1300 gtk_box_pack_start(GTK_BOX(hbox), ui_data->combo, TRUE, TRUE, 0);
1301 g_signal_connect_swapped(ui_data->combo, "changed",
1302 G_CALLBACK(queue_connect_host), ui_data);
1303 gtk_widget_show(ui_data->combo);
1305 ui_data->connect_button = gtk_button_new();
1306 gtk_box_pack_end(GTK_BOX(hbox), ui_data->connect_button, FALSE, FALSE, 0);
1307 image = gtk_image_new_from_stock(GTK_STOCK_CONNECT, GTK_ICON_SIZE_BUTTON);
1308 gtk_button_set_image (GTK_BUTTON(ui_data->connect_button), image);
1309 gtk_widget_grab_focus(ui_data->connect_button);
1310 g_signal_connect_swapped(ui_data->connect_button, "clicked",
1311 G_CALLBACK(queue_connect_host), ui_data);
1312 gtk_widget_show(ui_data->connect_button);
1314 autocon = gtk_check_button_new_with_label("Automatically start connecting next time");
1315 gtk_box_pack_start(GTK_BOX(vbox), autocon, FALSE, FALSE, 0);
1316 if (get_gconf_autoconnect(gcl, config_path))
1317 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autocon), 1);
1318 g_signal_connect(autocon, "toggled", G_CALLBACK(autocon_toggled), NULL);
1319 gtk_widget_show(autocon);
1321 frame = gtk_frame_new(NULL);
1322 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
1323 gtk_widget_set_size_request(frame, -1, -1);
1324 gtk_widget_show(frame);
1326 frame_box = gtk_vbox_new(FALSE, 4);
1327 gtk_container_set_border_width(GTK_CONTAINER(frame_box), 8);
1328 gtk_container_add(GTK_CONTAINER(frame), frame_box);
1329 gtk_widget_show(frame_box);
1331 ui_data->no_form_label = gtk_label_new("Select a host to fetch the login form");
1332 gtk_widget_set_sensitive(ui_data->no_form_label, FALSE);
1333 gtk_box_pack_start(GTK_BOX(frame_box), ui_data->no_form_label, FALSE, FALSE, 0);
1334 gtk_widget_show(ui_data->no_form_label);
1336 ui_data->getting_form_label = gtk_label_new("Contacting host, please wait...");
1337 gtk_widget_set_sensitive(ui_data->getting_form_label, FALSE);
1338 gtk_box_pack_start(GTK_BOX(frame_box), ui_data->getting_form_label, FALSE, FALSE, 0);
1340 ui_data->ssl_box = gtk_vbox_new(FALSE, 4);
1341 gtk_box_pack_start(GTK_BOX(frame_box), ui_data->ssl_box, FALSE, FALSE, 0);
1342 gtk_widget_show(ui_data->ssl_box);
1344 hbox = gtk_hbox_new (FALSE, 6);
1345 gtk_box_pack_end(GTK_BOX(frame_box), hbox, FALSE, FALSE, 0);
1346 gtk_widget_show(hbox);
1348 ui_data->login_button = gtk_button_new_with_mnemonic("_Login");
1349 image = gtk_image_new_from_stock(GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON);
1350 gtk_button_set_image (GTK_BUTTON(ui_data->login_button), image);
1351 gtk_box_pack_end(GTK_BOX(hbox), ui_data->login_button, FALSE, FALSE, 0);
1352 g_signal_connect (ui_data->login_button, "clicked", G_CALLBACK(login_clicked), ui_data);
1353 gtk_widget_set_sensitive (ui_data->login_button, FALSE);
1354 gtk_widget_show(ui_data->login_button);
1356 ui_data->cancel_button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
1357 gtk_box_pack_end(GTK_BOX(hbox), ui_data->cancel_button, FALSE, FALSE, 0);
1358 g_signal_connect (ui_data->cancel_button, "clicked", G_CALLBACK(cancel_clicked), ui_data);
1359 gtk_widget_set_sensitive (ui_data->cancel_button, FALSE);
1360 gtk_widget_show(ui_data->cancel_button);
1362 exp = gtk_expander_new("Log");
1363 gtk_box_pack_end(GTK_BOX(vbox), exp, FALSE, FALSE, 0);
1364 gtk_widget_show(exp);
1366 scrolled = gtk_scrolled_window_new(NULL, NULL);
1367 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1368 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1369 gtk_widget_set_size_request(scrolled, -1, 75);
1370 gtk_container_add(GTK_CONTAINER(exp), scrolled);
1371 gtk_widget_show(scrolled);
1373 view = gtk_text_view_new();
1374 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
1375 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
1376 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
1377 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 5);
1378 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 5);
1379 gtk_text_view_set_indent(GTK_TEXT_VIEW(view), -10);
1380 gtk_container_add(GTK_CONTAINER(scrolled), view);
1381 gtk_widget_show(view);
1383 ui_data->log = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
1384 g_signal_connect(ui_data->log, "changed", G_CALLBACK(scroll_log), view);
1387 static auth_ui_data *init_ui_data (char *vpn_name)
1389 auth_ui_data *ui_data;
1391 ui_data = g_slice_new0(auth_ui_data);
1392 ui_data->retval = 1;
1394 ui_data->form_entries = g_queue_new();
1395 ui_data->form_mutex = g_mutex_new();
1396 ui_data->form_retval_changed = g_cond_new();
1397 ui_data->form_shown_changed = g_cond_new();
1398 ui_data->cert_response_changed = g_cond_new();
1399 ui_data->vpn_name = vpn_name;
1401 ui_data->vpninfo = g_slice_new0(struct openconnect_info);
1402 ui_data->vpninfo->mtu = 1406;
1403 ui_data->vpninfo->useragent = openconnect_create_useragent("OpenConnect VPN Agent (NetworkManager)");
1404 ui_data->vpninfo->ssl_fd = -1;
1405 ui_data->vpninfo->write_new_config = write_new_config;
1406 ui_data->vpninfo->progress = write_progress;
1407 ui_data->vpninfo->validate_peer_cert = validate_peer_cert;
1408 ui_data->vpninfo->process_auth_form = nm_process_auth_form;
1410 ui_data->vpninfo->proxy_factory = px_proxy_factory_new();
1416 static struct option long_options[] = {
1417 {"reprompt", 0, 0, 'r'},
1418 {"uuid", 1, 0, 'u'},
1419 {"name", 1, 0, 'n'},
1420 {"service", 1, 0, 's'},
1424 int main (int argc, char **argv)
1426 char *vpn_name = NULL, *vpn_uuid = NULL, *vpn_service = NULL;
1430 while ((opt = getopt_long(argc, argv, "ru:n:s:", long_options, NULL))) {
1448 vpn_service = optarg;
1452 fprintf(stderr, "Unknown option\n");
1457 if (optind != argc) {
1458 fprintf(stderr, "Superfluous command line options\n");
1462 if (!vpn_uuid || !vpn_name || !vpn_service) {
1463 fprintf (stderr, "Have to supply UUID, name, and service\n");
1467 if (strcmp(vpn_service, NM_DBUS_SERVICE_OPENCONNECT) != 0) {
1468 fprintf (stderr, "This dialog only works with the '%s' service\n",
1469 NM_DBUS_SERVICE_OPENCONNECT);
1473 g_thread_init (NULL);
1476 ui_data = init_ui_data(vpn_name);
1477 if (get_config(vpn_uuid, ui_data->vpninfo)) {
1478 fprintf(stderr, "Failed to find VPN UUID %s in gconf\n", vpn_uuid);
1481 build_main_dialog(ui_data);
1484 openconnect_init_openssl();
1486 if (get_gconf_autoconnect(gcl, config_path))
1487 queue_connect_host(ui_data);
1489 gtk_window_present(GTK_WINDOW(ui_data->dialog));
1492 return ui_data->retval;