GCC Code Coverage Report


Directory: ./
File: panels/network/connection-editor/net-connection-editor.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 453 0.0%
Functions: 0 43 0.0%
Branches: 0 183 0.0%

Line Branch Exec Source
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2012 Red Hat, Inc
4 *
5 * Licensed under the GNU General Public License Version 2
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "config.h"
23
24 #include <glib-object.h>
25 #include <glib/gi18n.h>
26
27 #include <NetworkManager.h>
28
29 #include "net-connection-editor.h"
30 #include "net-connection-editor-resources.h"
31 #include "ce-page.h"
32 #include "ce-page-details.h"
33 #include "ce-page-wifi.h"
34 #include "ce-page-ip4.h"
35 #include "ce-page-ip6.h"
36 #include "ce-page-security.h"
37 #include "ce-page-ethernet.h"
38 #include "ce-page-bluetooth.h"
39 #include "ce-page-8021x-security.h"
40 #include "ce-page-vpn.h"
41 #include "ce-page-wireguard.h"
42 #include "vpn-helpers.h"
43
44 enum {
45 DONE,
46 LAST_SIGNAL
47 };
48
49 static guint signals[LAST_SIGNAL] = { 0 };
50
51 struct _NetConnectionEditor
52 {
53 AdwWindow parent;
54
55 GtkBox *add_connection_box;
56 AdwBin *add_connection_frame;
57 GtkButton *apply_button;
58 GtkButton *cancel_button;
59 GtkNotebook *notebook;
60 AdwToastOverlay *toast_overlay;
61 GtkStack *toplevel_stack;
62
63 NMClient *client;
64 NMDevice *device;
65
66 NMConnection *connection;
67 NMConnection *orig_connection;
68 gboolean is_new_connection;
69 gboolean is_changed;
70 NMAccessPoint *ap;
71 GCancellable *cancellable;
72
73 GSList *initializing_pages;
74
75 NMClientPermissionResult can_modify;
76
77 gboolean title_set;
78 };
79
80 G_DEFINE_TYPE (NetConnectionEditor, net_connection_editor, ADW_TYPE_WINDOW)
81
82 /* Used as both GSettings keys and GObject data tags */
83 #define IGNORE_CA_CERT_TAG "ignore-ca-cert"
84 #define IGNORE_PHASE2_CA_CERT_TAG "ignore-phase2-ca-cert"
85
86 static GSettings *
87 _get_ca_ignore_settings (NMConnection *connection)
88 {
89 GSettings *settings;
90 g_autofree gchar *path = NULL;
91 const char *uuid;
92
93 g_return_val_if_fail (connection, NULL);
94
95 uuid = nm_connection_get_uuid (connection);
96 g_return_val_if_fail (uuid && *uuid, NULL);
97
98 path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s/", uuid);
99 settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path);
100
101 return settings;
102 }
103
104 /**
105 * eap_method_ca_cert_ignore_save:
106 * @connection: the connection for which to save CA cert ignore values to GSettings
107 *
108 * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
109 * then to GSettings if present, using the connection UUID as the index.
110 */
111 static void
112 eap_method_ca_cert_ignore_save (NMConnection *connection)
113 {
114 NMSetting8021x *s_8021x;
115 g_autoptr(GSettings) settings = NULL;
116 gboolean ignore = FALSE, phase2_ignore = FALSE;
117
118 g_return_if_fail (connection);
119
120 s_8021x = nm_connection_get_setting_802_1x (connection);
121 if (s_8021x) {
122 ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_CA_CERT_TAG);
123 phase2_ignore = !!g_object_get_data (G_OBJECT (s_8021x), IGNORE_PHASE2_CA_CERT_TAG);
124 }
125
126 settings = _get_ca_ignore_settings (connection);
127 if (!settings)
128 return;
129
130 g_settings_set_boolean (settings, IGNORE_CA_CERT_TAG, ignore);
131 g_settings_set_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG, phase2_ignore);
132 }
133
134 /**
135 * eap_method_ca_cert_ignore_load:
136 * @connection: the connection for which to load CA cert ignore values to GSettings
137 *
138 * Reads the CA cert ignore tags from the 802.1x setting GObject data and saves
139 * then to GSettings if present, using the connection UUID as the index.
140 */
141 static void
142 eap_method_ca_cert_ignore_load (NMConnection *connection)
143 {
144 g_autoptr(GSettings) settings = NULL;
145 NMSetting8021x *s_8021x;
146 gboolean ignore, phase2_ignore;
147
148 g_return_if_fail (connection);
149
150 s_8021x = nm_connection_get_setting_802_1x (connection);
151 if (!s_8021x)
152 return;
153
154 settings = _get_ca_ignore_settings (connection);
155 if (!settings)
156 return;
157
158 ignore = g_settings_get_boolean (settings, IGNORE_CA_CERT_TAG);
159 phase2_ignore = g_settings_get_boolean (settings, IGNORE_PHASE2_CA_CERT_TAG);
160
161 g_object_set_data (G_OBJECT (s_8021x),
162 IGNORE_CA_CERT_TAG,
163 GUINT_TO_POINTER (ignore));
164 g_object_set_data (G_OBJECT (s_8021x),
165 IGNORE_PHASE2_CA_CERT_TAG,
166 GUINT_TO_POINTER (phase2_ignore));
167 }
168
169 static void page_changed (NetConnectionEditor *self);
170
171 static void
172 cancel_editing (NetConnectionEditor *self)
173 {
174 g_signal_emit (self, signals[DONE], 0, FALSE);
175 gtk_window_destroy (GTK_WINDOW (self));
176 }
177
178 static gboolean
179 net_connection_editor_close_request (GtkWindow *window)
180 {
181 cancel_editing (NET_CONNECTION_EDITOR (window));
182
183 return GTK_WINDOW_CLASS (net_connection_editor_parent_class)->close_request (window);
184 }
185
186 static void
187 cancel_clicked_cb (NetConnectionEditor *self)
188 {
189 cancel_editing (self);
190 }
191
192 static void
193 update_connection (NetConnectionEditor *self)
194 {
195 g_autoptr(GVariant) settings = NULL;
196
197 settings = nm_connection_to_dbus (self->connection, NM_CONNECTION_SERIALIZE_ALL);
198 nm_connection_replace_settings (self->orig_connection, settings, NULL);
199 }
200
201 static void
202 update_complete (NetConnectionEditor *self,
203 gboolean success)
204 {
205 g_signal_emit (self, signals[DONE], 0, success);
206 gtk_window_destroy (GTK_WINDOW (self));
207 }
208
209 static void
210 device_reapply_cb (GObject *source_object,
211 GAsyncResult *res,
212 gpointer user_data)
213 {
214 NetConnectionEditor *self = user_data;
215 g_autoptr(GError) error = NULL;
216 gboolean success = TRUE;
217
218 if (!nm_device_reapply_finish (NM_DEVICE (source_object), res, &error)) {
219 if (!g_error_matches (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_NOT_ACTIVE))
220 g_warning ("Failed to reapply changes on device: %s", error->message);
221 success = FALSE;
222 }
223
224 update_complete (self, success);
225 g_object_unref (self);
226 }
227
228 static void
229 updated_connection_cb (GObject *source_object,
230 GAsyncResult *res,
231 gpointer user_data)
232 {
233 NetConnectionEditor *self = user_data;
234 g_autoptr(GError) error = NULL;
235
236 if (!nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (source_object),
237 res, &error)) {
238 g_warning ("Failed to commit changes: %s", error->message);
239 update_complete (self, FALSE);
240 g_object_unref (self);
241 return;
242 }
243
244 nm_connection_clear_secrets (NM_CONNECTION (source_object));
245
246 if (!self->device) {
247 update_complete (self, TRUE);
248 g_object_unref (self);
249 return;
250 }
251
252 nm_device_reapply_async (self->device, NM_CONNECTION (self->orig_connection),
253 0, 0, NULL, device_reapply_cb, self /* owned */);
254 }
255
256 static void
257 added_connection_cb (GObject *source_object,
258 GAsyncResult *res,
259 gpointer user_data)
260 {
261 NetConnectionEditor *self = user_data;
262 g_autoptr(GError) error = NULL;
263
264 if (!nm_client_add_connection_finish (NM_CLIENT (source_object), res, &error)) {
265 g_warning ("Failed to add connection: %s", error->message);
266 update_complete (self, FALSE);
267 g_object_unref (self);
268 return;
269 }
270
271 if (!self->device) {
272 update_complete (self, TRUE);
273 g_object_unref (self);
274 return;
275 }
276
277 nm_device_reapply_async (self->device, NM_CONNECTION (self->orig_connection),
278 0, 0, NULL, device_reapply_cb, self /* owned */);
279 }
280
281 static void
282 apply_clicked_cb (NetConnectionEditor *self)
283 {
284 update_connection (self);
285
286 eap_method_ca_cert_ignore_save (self->connection);
287
288 if (self->is_new_connection) {
289 nm_client_add_connection_async (self->client,
290 self->orig_connection,
291 TRUE,
292 NULL,
293 added_connection_cb,
294 g_object_ref (self));
295 } else {
296 nm_remote_connection_commit_changes_async (NM_REMOTE_CONNECTION (self->orig_connection),
297 TRUE,
298 NULL,
299 updated_connection_cb,
300 g_object_ref (self));
301 }
302
303 gtk_widget_set_visible (GTK_WIDGET (self), FALSE);
304 }
305
306 static void
307 net_connection_editor_init (NetConnectionEditor *self)
308 {
309 gtk_widget_init_template (GTK_WIDGET (self));
310 }
311
312 static void
313 net_connection_editor_finalize (GObject *object)
314 {
315 NetConnectionEditor *self = NET_CONNECTION_EDITOR (object);
316
317 g_clear_object (&self->connection);
318 g_clear_object (&self->orig_connection);
319 g_clear_object (&self->device);
320 g_clear_object (&self->client);
321 g_clear_object (&self->ap);
322 g_cancellable_cancel (self->cancellable);
323 g_clear_object (&self->cancellable);
324
325 G_OBJECT_CLASS (net_connection_editor_parent_class)->finalize (object);
326 }
327
328 static void
329 net_connection_editor_class_init (NetConnectionEditorClass *class)
330 {
331 GObjectClass *object_class = G_OBJECT_CLASS (class);
332 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
333 GtkWindowClass *window_class = GTK_WINDOW_CLASS (class);
334
335 g_resources_register (net_connection_editor_get_resource ());
336
337 object_class->finalize = net_connection_editor_finalize;
338
339 window_class->close_request = net_connection_editor_close_request;
340
341 signals[DONE] = g_signal_new ("done",
342 G_OBJECT_CLASS_TYPE (object_class),
343 G_SIGNAL_RUN_FIRST,
344 0,
345 NULL, NULL,
346 NULL,
347 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
348
349 gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
350
351 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/connection-editor.ui");
352
353 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_box);
354 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, add_connection_frame);
355 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, apply_button);
356 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, cancel_button);
357 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, notebook);
358 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toast_overlay);
359 gtk_widget_class_bind_template_child (widget_class, NetConnectionEditor, toplevel_stack);
360
361 gtk_widget_class_bind_template_callback (widget_class, cancel_clicked_cb);
362 gtk_widget_class_bind_template_callback (widget_class, apply_clicked_cb);
363 }
364
365 static void
366 nm_connection_editor_watch_cb (GPid pid,
367 gint status,
368 gpointer user_data)
369 {
370 g_debug ("Child %d" G_PID_FORMAT " exited %s", pid,
371 g_spawn_check_wait_status (status, NULL) ? "normally" : "abnormally");
372
373 g_spawn_close_pid (pid);
374 /* Close the dialog when nm-connection-editor exits. */
375 gtk_window_destroy (GTK_WINDOW (user_data));
376 }
377
378 static void
379 net_connection_editor_do_fallback (NetConnectionEditor *self, const gchar *type)
380 {
381 g_autoptr(GError) error = NULL;
382 g_autoptr(GStrvBuilder) builder = NULL;
383 g_auto(GStrv) argv = NULL;
384 GPid child_pid;
385
386 builder = g_strv_builder_new ();
387 g_strv_builder_add (builder, "nm-connection-editor");
388
389 if (self->is_new_connection) {
390 g_autofree gchar *type_str = NULL;
391
392 type_str = g_strdup_printf ("--type=%s", type);
393 g_strv_builder_add (builder, type_str);
394 g_strv_builder_add (builder, "--create");
395 } else {
396 g_autofree gchar *edit_str = NULL;
397
398 edit_str = g_strdup_printf ("--edit=%s", nm_connection_get_uuid (self->connection));
399 g_strv_builder_add (builder, edit_str);
400 }
401
402 g_strv_builder_add (builder, NULL);
403 argv = g_strv_builder_end (builder);
404
405 g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
406 NULL, NULL, &child_pid, NULL, NULL, NULL, &error);
407
408 if (error) {
409 AdwToast *toast;
410 g_autofree gchar *message = NULL;
411
412 message = g_strdup_printf (_("Unable to open connection editor: %s"), error->message);
413 toast = adw_toast_new (message);
414
415 adw_toast_overlay_add_toast (self->toast_overlay, toast);
416 } else {
417 g_child_watch_add (child_pid, nm_connection_editor_watch_cb, self);
418 }
419
420 g_signal_emit (self, signals[DONE], 0, FALSE);
421 }
422
423 static void
424 net_connection_editor_update_title (NetConnectionEditor *self)
425 {
426 g_autofree gchar *id = NULL;
427
428 if (self->title_set)
429 return;
430
431 if (self->is_new_connection) {
432 if (self->device) {
433 id = g_strdup (_("New Profile"));
434 } else {
435 /* Leave it set to "Add New Connection" */
436 return;
437 }
438 } else {
439 NMSettingWireless *sw;
440 sw = nm_connection_get_setting_wireless (self->connection);
441 if (sw) {
442 GBytes *ssid;
443 ssid = nm_setting_wireless_get_ssid (sw);
444 id = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
445 } else {
446 id = g_strdup (nm_connection_get_id (self->connection));
447 }
448 }
449 gtk_window_set_title (GTK_WINDOW (self), id);
450 }
451
452 static gboolean
453 editor_is_initialized (NetConnectionEditor *self)
454 {
455 return self->initializing_pages == NULL;
456 }
457
458 static void
459 update_sensitivity (NetConnectionEditor *self)
460 {
461 NMSettingConnection *sc;
462 gboolean sensitive;
463 gint i;
464
465 if (!editor_is_initialized (self))
466 return;
467
468 sc = nm_connection_get_setting_connection (self->connection);
469
470 if (nm_setting_connection_get_read_only (sc)) {
471 sensitive = FALSE;
472 } else {
473 sensitive = self->can_modify;
474 }
475
476 for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
477 GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
478 gtk_widget_set_sensitive (page, sensitive);
479 }
480 }
481
482 static void
483 validate (NetConnectionEditor *self)
484 {
485 gboolean valid = FALSE;
486 g_autofree gchar *apply_tooltip = NULL;
487 gint i;
488
489 if (!editor_is_initialized (self))
490 goto done;
491
492 valid = TRUE;
493 for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
494 CEPage *page = CE_PAGE (gtk_notebook_get_nth_page (self->notebook, i));
495 g_autoptr(GError) error = NULL;
496
497 if (!ce_page_validate (page, self->connection, &error)) {
498 valid = FALSE;
499 if (error) {
500 apply_tooltip = g_strdup_printf (_("Invalid setting %s: %s"), ce_page_get_title (page), error->message);
501 g_debug ("%s", apply_tooltip);
502 } else {
503 apply_tooltip = g_strdup_printf (_("Invalid setting %s"), ce_page_get_title (page));
504 g_debug ("%s", apply_tooltip);
505 }
506 }
507 }
508
509 update_sensitivity (self);
510 done:
511 if (apply_tooltip != NULL)
512 gtk_widget_set_tooltip_text(GTK_WIDGET (self->apply_button), apply_tooltip);
513
514 gtk_widget_set_sensitive (GTK_WIDGET (self->apply_button), valid && self->is_changed);
515 }
516
517 static void
518 page_changed (NetConnectionEditor *self)
519 {
520 if (editor_is_initialized (self))
521 self->is_changed = TRUE;
522 validate (self);
523 }
524
525 static gboolean
526 idle_validate (gpointer user_data)
527 {
528 validate (NET_CONNECTION_EDITOR (user_data));
529
530 return G_SOURCE_REMOVE;
531 }
532
533 static void
534 recheck_initialization (NetConnectionEditor *self)
535 {
536 if (!editor_is_initialized (self))
537 return;
538
539 gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->notebook));
540 gtk_notebook_set_current_page (self->notebook, 0);
541
542 g_idle_add (idle_validate, self);
543
544 if (self->is_new_connection)
545 adw_bin_set_child (self->add_connection_frame, NULL);
546
547 }
548
549 static void
550 page_initialized (NetConnectionEditor *self, GError *error, CEPage *page)
551 {
552 GtkWidget *label;
553 gint position;
554 gint i;
555
556 position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
557 g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
558 for (i = 0; i < gtk_notebook_get_n_pages (self->notebook); i++) {
559 GtkWidget *page = gtk_notebook_get_nth_page (self->notebook, i);
560 gint pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (page), "position"));
561 if (pos > position)
562 break;
563 }
564
565 label = gtk_label_new (ce_page_get_title (page));
566
567 gtk_notebook_insert_page (self->notebook, GTK_WIDGET (page), label, i);
568
569 self->initializing_pages = g_slist_remove (self->initializing_pages, page);
570
571 recheck_initialization (self);
572 }
573
574 typedef struct {
575 NetConnectionEditor *editor;
576 CEPage *page;
577 const gchar *setting_name;
578 } GetSecretsInfo;
579
580 static void
581 get_secrets_cb (GObject *source_object,
582 GAsyncResult *res,
583 gpointer user_data)
584 {
585 NMRemoteConnection *connection;
586 g_autofree GetSecretsInfo *info = user_data;
587 g_autoptr(GError) error = NULL;
588 g_autoptr(GVariant) variant = NULL;
589
590 connection = NM_REMOTE_CONNECTION (source_object);
591 variant = nm_remote_connection_get_secrets_finish (connection, res, &error);
592
593 if (!variant) {
594 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
595 return;
596
597 g_warning ("Failed to get secrets: %s", error->message);
598 }
599
600 ce_page_complete_init (info->page, info->editor->connection, info->setting_name, variant, g_steal_pointer (&error));
601 }
602
603 static void
604 get_secrets_for_page (NetConnectionEditor *self,
605 CEPage *page,
606 const gchar *setting_name)
607 {
608 GetSecretsInfo *info;
609
610 info = g_new0 (GetSecretsInfo, 1);
611 info->editor = self;
612 info->page = page;
613 info->setting_name = setting_name;
614
615 nm_remote_connection_get_secrets_async (NM_REMOTE_CONNECTION (self->orig_connection),
616 setting_name,
617 self->cancellable,
618 get_secrets_cb,
619 info);
620 }
621
622 static void
623 add_page (NetConnectionEditor *self, CEPage *page)
624 {
625 gint position;
626
627 position = g_slist_length (self->initializing_pages);
628 g_object_set_data (G_OBJECT (page), "position", GINT_TO_POINTER (position));
629
630 self->initializing_pages = g_slist_append (self->initializing_pages, page);
631
632 g_signal_connect_object (page, "changed", G_CALLBACK (page_changed), self, G_CONNECT_SWAPPED);
633 g_signal_connect_object (page, "initialized", G_CALLBACK (page_initialized), self, G_CONNECT_SWAPPED);
634 }
635
636 static void
637 net_connection_editor_set_connection (NetConnectionEditor *self,
638 NMConnection *connection)
639 {
640 GSList *pages, *l;
641 NMSettingConnection *sc;
642 const gchar *type;
643 gboolean is_wired;
644 gboolean is_wifi;
645 gboolean is_vpn;
646 gboolean is_wireguard;
647 gboolean is_bluetooth;
648
649 self->is_new_connection = !nm_client_get_connection_by_uuid (self->client,
650 nm_connection_get_uuid (connection));
651
652 if (self->is_new_connection) {
653 gtk_button_set_label (self->apply_button, _("_Add"));
654 self->is_changed = TRUE;
655 }
656
657 self->connection = nm_simple_connection_new_clone (connection);
658 self->orig_connection = g_object_ref (connection);
659
660 net_connection_editor_update_title (self);
661
662 eap_method_ca_cert_ignore_load (self->connection);
663
664 sc = nm_connection_get_setting_connection (connection);
665 type = nm_setting_connection_get_connection_type (sc);
666
667 is_wired = g_str_equal (type, NM_SETTING_WIRED_SETTING_NAME);
668 is_wifi = g_str_equal (type, NM_SETTING_WIRELESS_SETTING_NAME);
669 is_vpn = g_str_equal (type, NM_SETTING_VPN_SETTING_NAME);
670 is_wireguard = g_str_equal (type, NM_SETTING_WIREGUARD_SETTING_NAME);
671 is_bluetooth = g_str_equal (type, NM_SETTING_BLUETOOTH_SETTING_NAME);
672
673 add_page (self, CE_PAGE (ce_page_details_new (self->connection, self->device, self->ap, self, self->is_new_connection)));
674
675 if (is_wifi)
676 add_page (self, CE_PAGE (ce_page_wifi_new (self->connection, self->client)));
677 else if (is_wired)
678 add_page (self, CE_PAGE (ce_page_ethernet_new (self->connection, self->client)));
679 else if (is_vpn)
680 add_page (self, CE_PAGE (ce_page_vpn_new (self->connection)));
681 else if (is_wireguard)
682 add_page (self, CE_PAGE (ce_page_wireguard_new (self->connection)));
683 else if (is_bluetooth)
684 add_page (self, CE_PAGE (ce_page_bluetooth_new (self->connection)));
685 else {
686 /* Unsupported type */
687 net_connection_editor_do_fallback (self, type);
688 return;
689 }
690
691 add_page (self, CE_PAGE (ce_page_ip4_new (self->connection, self->client)));
692 add_page (self, CE_PAGE (ce_page_ip6_new (self->connection, self->client)));
693
694 if (is_wifi)
695 add_page (self, CE_PAGE (ce_page_security_new (self->connection)));
696 else if (is_wired)
697 add_page (self, CE_PAGE (ce_page_8021x_security_new (self->connection)));
698
699 pages = g_slist_copy (self->initializing_pages);
700 for (l = pages; l; l = l->next) {
701 CEPage *page = l->data;
702 const gchar *security_setting;
703
704 security_setting = ce_page_get_security_setting (page);
705 if (!security_setting || self->is_new_connection) {
706 ce_page_complete_init (page, NULL, NULL, NULL, NULL);
707 } else {
708 get_secrets_for_page (self, page, security_setting);
709 }
710 }
711 g_slist_free (pages);
712 }
713
714 static NMConnection *
715 complete_vpn_connection (NetConnectionEditor *self,
716 NMConnection *connection,
717 GType setting_type)
718 {
719 NMSettingConnection *s_con;
720 NMSetting *s_type;
721
722 if (!connection)
723 connection = nm_simple_connection_new ();
724
725 s_con = nm_connection_get_setting_connection (connection);
726 if (!s_con) {
727 s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
728 nm_connection_add_setting (connection, NM_SETTING (s_con));
729 }
730
731 if (!nm_setting_connection_get_uuid (s_con)) {
732 g_autofree gchar *uuid = nm_utils_uuid_generate ();
733 g_object_set (s_con,
734 NM_SETTING_CONNECTION_UUID, uuid,
735 NULL);
736 }
737
738 if (!nm_setting_connection_get_id (s_con)) {
739 const GPtrArray *connections;
740 g_autofree gchar *id = NULL;
741
742 connections = nm_client_get_connections (self->client);
743 id = ce_page_get_next_available_name (connections, NAME_FORMAT_TYPE, _("VPN"));
744 g_object_set (s_con,
745 NM_SETTING_CONNECTION_ID, id,
746 NULL);
747 }
748
749 s_type = nm_connection_get_setting (connection, setting_type);
750 if (!s_type) {
751 s_type = g_object_new (setting_type, NULL);
752 nm_connection_add_setting (connection, s_type);
753 }
754
755 if (!nm_setting_connection_get_connection_type (s_con)) {
756 g_object_set (s_con,
757 NM_SETTING_CONNECTION_TYPE, nm_setting_get_name (s_type),
758 NULL);
759 }
760
761 return connection;
762 }
763
764 static void
765 finish_add_connection (NetConnectionEditor *self, NMConnection *connection)
766 {
767 gtk_widget_set_visible (GTK_WIDGET (self->apply_button), TRUE);
768
769 if (connection)
770 net_connection_editor_set_connection (self, connection);
771 }
772
773 static void
774 vpn_import_complete (NMConnection *connection, gpointer user_data)
775 {
776 NetConnectionEditor *self = user_data;
777 NMSetting *s_type = NULL;
778 NMSettingConnection *s_con;
779
780 if (!connection) {
781 AdwToast *toast;
782 g_autofree gchar *message = NULL;
783
784 message = g_strdup_printf (_("Invalid VPN configuration file"));
785 toast = adw_toast_new (message);
786 adw_toast_overlay_add_toast (self->toast_overlay, toast);
787
788 g_signal_emit (self, signals[DONE], 0, FALSE);
789 return;
790 }
791
792 s_type = nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD);
793 if (s_type)
794 complete_vpn_connection (self, connection, NM_TYPE_SETTING_WIREGUARD);
795 else
796 complete_vpn_connection (self, connection, NM_TYPE_SETTING_VPN);
797
798 /* Mark the connection as private to this user, and non-autoconnect */
799 s_con = nm_connection_get_setting_connection (connection);
800 g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
801 nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
802
803 finish_add_connection (self, connection);
804 }
805
806 static void
807 vpn_type_activated (NetConnectionEditor *self, GtkWidget *row)
808 {
809 const char *service_name = g_object_get_data (G_OBJECT (row), "service_name");
810 NMConnection *connection;
811 NMSettingVpn *s_vpn = NULL;
812 NMSettingConnection *s_con;
813 GType s_type = NM_TYPE_SETTING_VPN;
814
815 if (!strcmp (service_name, "import")) {
816 vpn_import (GTK_WINDOW (self), vpn_import_complete, self);
817 return;
818 } else if (!strcmp (service_name, "wireguard")) {
819 s_type = NM_TYPE_SETTING_WIREGUARD;
820 }
821
822 connection = complete_vpn_connection (self, NULL, s_type);
823 if (s_type == NM_TYPE_SETTING_VPN) {
824 s_vpn = nm_connection_get_setting_vpn (connection);
825 g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_name, NULL);
826 }
827
828 /* Mark the connection as private to this user, and non-autoconnect */
829 s_con = nm_connection_get_setting_connection (connection);
830 g_object_set (s_con, NM_SETTING_CONNECTION_AUTOCONNECT, FALSE, NULL);
831 nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL);
832
833 finish_add_connection (self, connection);
834 }
835
836 static void
837 select_vpn_type (NetConnectionEditor *self, GtkListBox *list)
838 {
839 GSList *vpn_plugins, *iter;
840 GtkWidget *row;
841 GtkWidget *child;
842
843 /* Get the available VPN types */
844 vpn_plugins = vpn_get_plugins ();
845
846 /* Remove the previous menu contents */
847 while ((child = gtk_widget_get_first_child (GTK_WIDGET (list))) != NULL)
848 gtk_list_box_remove (list, child);
849
850 /* Add the VPN types */
851 for (iter = vpn_plugins; iter; iter = iter->next) {
852 NMVpnEditorPlugin *plugin = nm_vpn_plugin_info_get_editor_plugin (iter->data);
853 g_autofree gchar *name = NULL;
854 g_autofree gchar *desc = NULL;
855 g_autofree gchar *desc_markup = NULL;
856 g_autofree gchar *service_name = NULL;
857
858 g_object_get (plugin,
859 NM_VPN_EDITOR_PLUGIN_NAME, &name,
860 NM_VPN_EDITOR_PLUGIN_DESCRIPTION, &desc,
861 NM_VPN_EDITOR_PLUGIN_SERVICE, &service_name,
862 NULL);
863 desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc);
864
865 row = adw_action_row_new ();
866 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
867 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), name);
868 adw_action_row_set_subtitle (ADW_ACTION_ROW (row), desc_markup);
869
870 g_object_set_data_full (G_OBJECT (row), "service_name", g_steal_pointer (&service_name), g_free);
871 gtk_list_box_append (list, row);
872 }
873
874 /* Translators: VPN add dialog Wireguard description */
875 gchar *desc = _("Free and open-source VPN solution designed for ease "
876 "of use, high speed performance and low attack surface.");
877 gchar *desc_markup = g_markup_printf_escaped ("<span size='smaller'>%s</span>", desc);
878
879 row = adw_action_row_new ();
880 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
881 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("WireGuard"));
882 adw_action_row_set_subtitle (ADW_ACTION_ROW (row), desc_markup);
883
884 g_object_set_data (G_OBJECT (row), "service_name", "wireguard");
885 gtk_list_box_append (list, row);
886
887 /* Import */
888 row = adw_action_row_new ();
889 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
890 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("Import from fileā€¦"));
891
892 g_object_set_data (G_OBJECT (row), "service_name", "import");
893 gtk_list_box_append (list, row);
894
895 g_signal_connect_object (list, "row-activated",
896 G_CALLBACK (vpn_type_activated), self, G_CONNECT_SWAPPED);
897 }
898
899 static void
900 net_connection_editor_add_connection (NetConnectionEditor *self)
901 {
902 GtkListBox *list;
903
904 list = GTK_LIST_BOX (gtk_list_box_new ());
905 gtk_list_box_set_selection_mode (list, GTK_SELECTION_NONE);
906 gtk_widget_add_css_class (GTK_WIDGET (list), "boxed-list");
907
908 select_vpn_type (self, list);
909
910 adw_bin_set_child (self->add_connection_frame, GTK_WIDGET (list));
911
912 gtk_stack_set_visible_child (self->toplevel_stack, GTK_WIDGET (self->add_connection_box));
913 gtk_widget_set_visible (GTK_WIDGET (self->apply_button), FALSE);
914 gtk_window_set_title (GTK_WINDOW (self), _("Add VPN"));
915 }
916
917 static void
918 permission_changed (NetConnectionEditor *self,
919 NMClientPermission permission,
920 NMClientPermissionResult result)
921 {
922 if (permission != NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM)
923 return;
924
925 if (result == NM_CLIENT_PERMISSION_RESULT_YES || result == NM_CLIENT_PERMISSION_RESULT_AUTH)
926 self->can_modify = TRUE;
927 else
928 self->can_modify = FALSE;
929
930 validate (self);
931 }
932
933 NetConnectionEditor *
934 net_connection_editor_new (NMConnection *connection,
935 NMDevice *device,
936 NMAccessPoint *ap,
937 NMClient *client)
938 {
939 NetConnectionEditor *self;
940
941 self = g_object_new (net_connection_editor_get_type (), NULL);
942
943 self->cancellable = g_cancellable_new ();
944
945 if (ap)
946 self->ap = g_object_ref (ap);
947 if (device)
948 self->device = g_object_ref (device);
949 self->client = g_object_ref (client);
950
951 self->can_modify = nm_client_get_permission_result (client, NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM);
952 g_signal_connect_object (self->client, "permission-changed",
953 G_CALLBACK (permission_changed), self, G_CONNECT_SWAPPED);
954
955 if (connection)
956 net_connection_editor_set_connection (self, connection);
957 else
958 net_connection_editor_add_connection (self);
959
960 return self;
961 }
962
963 static void
964 forgotten_cb (GObject *source_object,
965 GAsyncResult *res,
966 gpointer user_data)
967 {
968 NMRemoteConnection *connection = NM_REMOTE_CONNECTION (source_object);
969 NetConnectionEditor *self = user_data;
970 g_autoptr(GError) error = NULL;
971
972 if (!nm_remote_connection_delete_finish (connection, res, &error)) {
973 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
974 g_warning ("Failed to delete connection %s: %s",
975 nm_connection_get_id (NM_CONNECTION (connection)),
976 error->message);
977 return;
978 }
979
980 cancel_editing (self);
981 }
982
983 void
984 net_connection_editor_forget (NetConnectionEditor *self)
985 {
986 nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (self->orig_connection),
987 self->cancellable, forgotten_cb, self);
988 }
989
990 void
991 net_connection_editor_set_title (NetConnectionEditor *self,
992 const gchar *title)
993 {
994 gtk_window_set_title (GTK_WINDOW (self), title);
995 self->title_set = TRUE;
996 }
997