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