nm: Make EAP-TLS configurable
[strongswan.git] / src / frontends / gnome / auth-dialog / main.c
1 /*
2 * Copyright (C) 2015 Lubomir Rintel
3 *
4 * Copyright (C) 2013-2016 Tobias Brunner
5 * Copyright (C) 2008-2011 Martin Willi
6 * HSR Hochschule fuer Technik Rapperswil
7 *
8 * Copyright (C) 2004 Dan Williams
9 * Red Hat, Inc.
10 *
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2 of the License, or (at your
14 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 * for more details.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <libsecret/secret.h>
32
33 #include <NetworkManager.h>
34 #include <nm-vpn-service-plugin.h>
35 #include <nma-vpn-password-dialog.h>
36
37 #define NM_DBUS_SERVICE_STRONGSWAN "org.freedesktop.NetworkManager.strongswan"
38
39 #define KEYRING_UUID_TAG "connection-uuid"
40 #define KEYRING_SN_TAG "setting-name"
41 #define KEYRING_SK_TAG "setting-key"
42
43 static const SecretSchema network_manager_secret_schema = {
44 "org.freedesktop.NetworkManager.Connection",
45 SECRET_SCHEMA_DONT_MATCH_NAME,
46 {
47 { KEYRING_UUID_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
48 { KEYRING_SN_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
49 { KEYRING_SK_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
50 { NULL, 0 },
51 }
52 };
53
54 #define UI_KEYFILE_GROUP "VPN Plugin UI"
55
56 static char *keyring_lookup_secret(const char *uuid, const char *secret_name)
57 {
58 GHashTable *attrs;
59 GList *list;
60 char *secret = NULL;
61
62 attrs = secret_attributes_build(&network_manager_secret_schema,
63 KEYRING_UUID_TAG, uuid,
64 KEYRING_SN_TAG, NM_SETTING_VPN_SETTING_NAME,
65 KEYRING_SK_TAG, secret_name,
66 NULL);
67
68 list = secret_service_search_sync (NULL, &network_manager_secret_schema, attrs,
69 SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
70 NULL, NULL);
71 if (list && list->data)
72 {
73 SecretItem *item = list->data;
74 SecretValue *value = secret_item_get_secret (item);
75
76 if (value)
77 {
78 secret = g_strdup (secret_value_get (value, NULL));
79 secret_value_unref (value);
80 }
81 }
82
83 g_list_free_full (list, g_object_unref);
84 g_hash_table_unref (attrs);
85 return secret;
86 }
87
88 static void keyfile_add_entry_info(GKeyFile *keyfile, const gchar *key, const gchar *value,
89 const gchar *label, gboolean is_secret, gboolean should_ask)
90 {
91 g_key_file_set_string (keyfile, key, "Value", value);
92 g_key_file_set_string (keyfile, key, "Label", label);
93 g_key_file_set_boolean (keyfile, key, "IsSecret", is_secret);
94 g_key_file_set_boolean (keyfile, key, "ShouldAsk", should_ask);
95 }
96
97 static void keyfile_print_stdout (GKeyFile *keyfile)
98 {
99 gchar *data;
100 gsize length;
101
102 data = g_key_file_to_data (keyfile, &length, NULL);
103
104 fputs (data, stdout);
105
106 g_free (data);
107 }
108
109 static gboolean get_secrets(const char *type, const char *cert_source, const char *uuid, const char *name,
110 gboolean retry, gboolean allow_interaction, gboolean external_ui_mode,
111 const char *in_pw, char **out_pw, NMSettingSecretFlags flags)
112 {
113 NMAVpnPasswordDialog *dialog;
114 char *prompt, *pw = NULL;
115 const char *new_pw = NULL;
116 guint32 minlen = 0;
117
118 if (!(flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) &&
119 !(flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
120 {
121 if (in_pw)
122 {
123 pw = g_strdup (in_pw);
124 }
125 else
126 {
127 pw = keyring_lookup_secret (uuid, "password");
128 }
129 }
130 if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
131 {
132 g_free (pw);
133 return TRUE;
134 }
135 if (!strcmp(type, "eap"))
136 {
137 prompt = g_strdup_printf (_("EAP password required to establish VPN connection '%s'."),
138 name);
139 }
140 else if (!strcmp(type, "psk"))
141 {
142 prompt = g_strdup_printf (_("Pre-shared key required to establish VPN connection '%s' (min. 20 characters)."),
143 name);
144 minlen = 20;
145 }
146 else /* certificate auth of some kind */
147 {
148 if (!strcmp(cert_source, "smartcard"))
149 {
150 prompt = g_strdup_printf (_("Smartcard PIN required to establish VPN connection '%s'."),
151 name);
152 }
153 else
154 {
155 prompt = g_strdup_printf (_("Private key decryption password required to establish VPN connection '%s'."),
156 name);
157 }
158 }
159 if (external_ui_mode)
160 {
161 GKeyFile *keyfile;
162
163 keyfile = g_key_file_new ();
164
165 g_key_file_set_integer (keyfile, UI_KEYFILE_GROUP, "Version", 2);
166 g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Description", prompt);
167 g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Title", _("Authenticate VPN"));
168
169 keyfile_add_entry_info (keyfile, "password", pw ?: "", _("Password:"), TRUE, allow_interaction);
170
171 keyfile_print_stdout (keyfile);
172 g_key_file_unref (keyfile);
173 goto out;
174 }
175 else if (!allow_interaction ||
176 (!retry && pw && !(flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)))
177 {
178 /* If we can't prompt the user, just return the existing password. Do the same
179 * if we don't nee a new password (!retry) and have an existing saved one */
180 *out_pw = pw;
181 g_free (prompt);
182 return TRUE;
183 }
184
185 dialog = (NMAVpnPasswordDialog*)nma_vpn_password_dialog_new(_("Authenticate VPN"), prompt, NULL);
186 nma_vpn_password_dialog_set_show_password_secondary(dialog, FALSE);
187
188 if (pw && !(flags & NM_SETTING_SECRET_FLAG_NOT_SAVED))
189 {
190 nma_vpn_password_dialog_set_password(dialog, pw);
191 }
192 gtk_widget_show (GTK_WIDGET (dialog));
193
194 too_short_retry:
195 if (nma_vpn_password_dialog_run_and_block (dialog))
196 {
197 new_pw = nma_vpn_password_dialog_get_password(dialog);
198 if (new_pw && minlen && strlen(new_pw) < minlen)
199 {
200 goto too_short_retry;
201 }
202 else if (new_pw)
203 {
204 *out_pw = g_strdup (new_pw);
205 }
206 }
207 gtk_widget_hide (GTK_WIDGET (dialog));
208 gtk_widget_destroy (GTK_WIDGET (dialog));
209 out:
210 g_free (prompt);
211 return TRUE;
212 }
213
214 static void print_secret (const char *secret_name, gchar *secret)
215 {
216 if (secret)
217 {
218 printf("%s\n%s\n", secret_name, secret);
219 g_free(secret);
220 }
221 printf("\n\n");
222 fflush(stdout);
223 }
224
225 static void wait_for_quit (void)
226 {
227 GString *str;
228 char c;
229 ssize_t n;
230 time_t start;
231
232 str = g_string_sized_new (10);
233 start = time (NULL);
234 do {
235 errno = 0;
236 n = read (0, &c, 1);
237 if (n == 0 || (n < 0 && errno == EAGAIN))
238 g_usleep (G_USEC_PER_SEC / 10);
239 else if (n == 1) {
240 g_string_append_c (str, c);
241 if (strstr (str->str, "QUIT") || (str->len > 10))
242 break;
243 } else
244 break;
245 } while (time (NULL) < start + 20);
246 g_string_free (str, TRUE);
247 }
248
249 int main (int argc, char *argv[])
250 {
251 gboolean retry = FALSE, allow_interaction = FALSE, external_ui_mode = FALSE;
252 gboolean need_secret = FALSE;
253 gchar *name = NULL, *uuid = NULL, *service = NULL, *pass = NULL;
254 GHashTable *data = NULL, *secrets = NULL;
255 NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
256 GOptionContext *context;
257 char *agent, *type, *cert_source;
258 int status = 0;
259 GOptionEntry entries[] = {
260 { "reprompt", 'r', 0, G_OPTION_ARG_NONE, &retry, "Reprompt for passwords", NULL},
261 { "uuid", 'u', 0, G_OPTION_ARG_STRING, &uuid, "UUID of VPN connection", NULL},
262 { "name", 'n', 0, G_OPTION_ARG_STRING, &name, "Name of VPN connection", NULL},
263 { "service", 's', 0, G_OPTION_ARG_STRING, &service, "VPN service type", NULL},
264 { "allow-interaction", 'i', 0, G_OPTION_ARG_NONE, &allow_interaction, "Allow user interaction", NULL},
265 { "external-ui-mode", 0, 0, G_OPTION_ARG_NONE, &external_ui_mode, "External UI mode", NULL},
266 { NULL }
267 };
268
269 bindtextdomain(GETTEXT_PACKAGE, NULL);
270 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
271 textdomain(GETTEXT_PACKAGE);
272
273 gtk_init (&argc, &argv);
274
275 context = g_option_context_new ("- strongswan auth dialog");
276 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
277 g_option_context_parse (context, &argc, &argv, NULL);
278 g_option_context_free (context);
279
280 if (uuid == NULL || name == NULL || service == NULL)
281 {
282 fprintf (stderr, "Have to supply UUID, name, and service\n");
283 return 1;
284 }
285
286 if (strcmp(service, NM_DBUS_SERVICE_STRONGSWAN) != 0)
287 {
288 fprintf(stderr, "This dialog only works with the '%s' service\n",
289 NM_DBUS_SERVICE_STRONGSWAN);
290 return 1;
291 }
292
293 if (!nm_vpn_service_plugin_read_vpn_details (0, &data, &secrets))
294 {
295 fprintf(stderr, "Failed to read '%s' (%s) data and secrets from stdin.\n",
296 name, uuid);
297 return 1;
298 }
299
300 type = g_hash_table_lookup (data, "method");
301 if (!type)
302 {
303 fprintf(stderr, "Connection lookup failed\n");
304 status = 1;
305 goto out;
306 }
307 cert_source = g_hash_table_lookup (data, "cert-source") ?: type;
308
309 if (!strcmp(type, "cert") ||
310 !strcmp(type, "eap-tls") ||
311 !strcmp(type, "key") ||
312 !strcmp(type, "agent") ||
313 !strcmp(type, "smartcard"))
314 {
315 if (!strcmp(cert_source, "agent"))
316 {
317 agent = getenv("SSH_AUTH_SOCK");
318 if (agent)
319 {
320 if (external_ui_mode)
321 {
322 GKeyFile *keyfile;
323
324 keyfile = g_key_file_new ();
325
326 g_key_file_set_integer (keyfile, UI_KEYFILE_GROUP, "Version", 2);
327 g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Description", "SSH agent");
328 g_key_file_set_string (keyfile, UI_KEYFILE_GROUP, "Title", _("Authenticate VPN"));
329
330 keyfile_add_entry_info (keyfile, "agent", agent, "SSH agent socket", TRUE, FALSE);
331
332 keyfile_print_stdout (keyfile);
333 g_key_file_unref (keyfile);
334 }
335 else
336 {
337 print_secret("agent", g_strdup (agent));
338 wait_for_quit ();
339 }
340 }
341 else if (allow_interaction)
342 {
343 GtkWidget *dialog;
344 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR,
345 GTK_BUTTONS_OK,
346 _("Configuration uses ssh-agent for authentication, "
347 "but ssh-agent is not running!"));
348 gtk_dialog_run (GTK_DIALOG (dialog));
349 gtk_widget_destroy (dialog);
350 }
351 }
352 else
353 {
354 need_secret = TRUE;
355 }
356 }
357
358 if (need_secret ||
359 !strcmp(type, "eap") ||
360 !strcmp(type, "psk"))
361 {
362 nm_vpn_service_plugin_get_secret_flags (secrets, "password", &flags);
363 if (!get_secrets(type, cert_source, uuid, name, retry, allow_interaction,
364 external_ui_mode, g_hash_table_lookup (secrets, "password"), &pass, flags))
365 {
366 status = 1;
367 }
368 else if (!external_ui_mode)
369 {
370 print_secret("password", pass);
371 wait_for_quit ();
372 }
373 }
374
375 out:
376 if (data)
377 {
378 g_hash_table_unref (data);
379 }
380 if (secrets)
381 {
382 g_hash_table_unref(secrets);
383 }
384 return status;
385 }