diff --git a/panels/system/cc-gnome-remote-desktop.c b/panels/system/cc-gnome-remote-desktop.c new file mode 100644 index 0000000000000000000000000000000000000000..ef7bc5303d89ec3520022dee4efc60184073a17f --- /dev/null +++ b/panels/system/cc-gnome-remote-desktop.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "config.h" + +#include "cc-gnome-remote-desktop.h" + +const SecretSchema * +cc_grd_rdp_credentials_get_schema (void) +{ + static const SecretSchema grd_rdp_credentials_schema = { + .name = "org.gnome.RemoteDesktop.RdpCredentials", + .flags = SECRET_SCHEMA_NONE, + .attributes = { + { "credentials", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "NULL", 0 }, + }, + }; + + return &grd_rdp_credentials_schema; +} + +void +cc_grd_store_rdp_credentials (const gchar *username, + const gchar *password, + GCancellable *cancellable) +{ + GVariantBuilder builder; + char *credentials; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "username", g_variant_new_string (username)); + g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password)); + credentials = g_variant_print (g_variant_builder_end (&builder), TRUE); + + secret_password_store_sync (CC_GRD_RDP_CREDENTIALS_SCHEMA, + SECRET_COLLECTION_DEFAULT, + "GNOME Remote Desktop RDP credentials", + credentials, + NULL, NULL, + NULL); + + g_free (credentials); +} + +gchar * +cc_grd_lookup_rdp_username (GCancellable *cancellable) +{ + g_autoptr(GError) error = NULL; + gchar *username = NULL; + g_autofree gchar *secret; + GVariant *variant = NULL; + + secret = secret_password_lookup_sync (CC_GRD_RDP_CREDENTIALS_SCHEMA, + cancellable, &error, + NULL); + if (error) { + g_warning ("Failed to get username: %s", error->message); + return NULL; + } + + if (secret == NULL) { + g_debug ("No RDP credentials available"); + return NULL; + } + + variant = g_variant_parse (NULL, secret, NULL, NULL, &error); + if (variant == NULL) { + g_warning ("Invalid credentials format in the keyring: %s", error->message); + return NULL; + } + + g_variant_lookup (variant, "username", "&s", &username); + + return username; +} + +gchar * +cc_grd_lookup_rdp_password (GCancellable *cancellable) +{ + g_autoptr(GError) error = NULL; + g_autofree gchar *secret; + gchar *password = NULL; + GVariant *variant = NULL; + + secret = secret_password_lookup_sync (CC_GRD_RDP_CREDENTIALS_SCHEMA, + cancellable, &error, + NULL); + if (error) { + g_warning ("Failed to get password: %s", error->message); + return NULL; + } + + if (secret == NULL) { + g_debug ("No RDP credentials available"); + return NULL; + } + + variant = g_variant_parse (NULL, secret, NULL, NULL, &error); + if (variant == NULL) { + g_warning ("Invalid credentials format in the keyring: %s", error->message); + return NULL; + } + + g_variant_lookup (variant, "password", "&s", &password); + + return password; +} diff --git a/panels/system/cc-gnome-remote-desktop.h b/panels/system/cc-gnome-remote-desktop.h new file mode 100644 index 0000000000000000000000000000000000000000..592da3497f7f7af65989c2755bfda8812be4f6b7 --- /dev/null +++ b/panels/system/cc-gnome-remote-desktop.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +const SecretSchema * cc_grd_rdp_credentials_get_schema (void); +#define CC_GRD_RDP_CREDENTIALS_SCHEMA cc_grd_rdp_credentials_get_schema () + +void cc_grd_store_rdp_credentials (const gchar *username, + const gchar *password, + GCancellable *cancellable); + +gchar * cc_grd_lookup_rdp_username (GCancellable *cancellable); +gchar * cc_grd_lookup_rdp_password (GCancellable *cancellable); + +G_END_DECLS diff --git a/panels/system/cc-system-panel.c b/panels/system/cc-system-panel.c index 93910cff39322b0a975162cb3245b7a241fe5738..aaadacc360bddfdcb28d35b594eb0980d8c1bc4b 100644 --- a/panels/system/cc-system-panel.c +++ b/panels/system/cc-system-panel.c @@ -27,25 +27,116 @@ #include "cc-system-panel.h" #include "cc-system-resources.h" +#include "cc-system-remote-desktop-page.h" + struct _CcSystemPanel { CcPanel parent_instance; - + + AdwHeaderBar *titlebar; + AdwWindowTitle *page_title; + GtkButton *back_button; + AdwPreferencesPage *main_page; AdwLeaflet *main_leaflet; + CcListRow *remote_desktop_row; + + CcSystemRemoteDesktopPage *remote_desktop_page; }; CC_PANEL_REGISTER (CcSystemPanel, cc_system_panel) +static void +system_panel_update_back_button_cb (CcSystemPanel *self) +{ + GtkWidget *page; + gboolean folded, is_main_page; + + g_assert (CC_IS_SYSTEM_PANEL (self)); + + folded = cc_panel_get_folded (CC_PANEL (self)); + + page = adw_leaflet_get_visible_child (self->main_leaflet); + is_main_page = page == (gpointer)self->main_page; + + gtk_widget_set_visible (GTK_WIDGET (self->back_button), folded || !is_main_page); +} + +static void +system_panel_back_clicked_cb (CcSystemPanel *self) +{ + GtkWidget *page; + gboolean is_main_page; + + g_assert (CC_IS_SYSTEM_PANEL (self)); + + page = adw_leaflet_get_visible_child (self->main_leaflet); + is_main_page = page == (gpointer)self->main_page; + + if (is_main_page) + gtk_widget_activate_action (GTK_WIDGET (self), "window.navigate", "i", + ADW_NAVIGATION_DIRECTION_BACK); + else + adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_BACK); +} + +static void +system_panel_visible_child_changed_cb (CcSystemPanel *self) +{ + GtkWidget *page; + const char *title = NULL; + + g_assert (CC_IS_SYSTEM_PANEL (self)); + + page = adw_leaflet_get_visible_child (self->main_leaflet); + + if (page == (gpointer)self->remote_desktop_page) + title = adw_preferences_page_get_title (ADW_PREFERENCES_PAGE (self->remote_desktop_page)); + else + title = _("System"); + + adw_window_title_set_title (self->page_title, title); +} + +static void +system_panel_row_activated_cb (CcSystemPanel *self, CcListRow *row) +{ + GtkWidget *child; + + g_assert (CC_IS_SYSTEM_PANEL (self)); + g_assert (CC_IS_LIST_ROW (row)); + + if (row == self->remote_desktop_row) + child = GTK_WIDGET (self->remote_desktop_page); + else + g_assert_not_reached (); + + adw_leaflet_set_visible_child (self->main_leaflet, child); +} + static void cc_system_panel_class_init (CcSystemPanelClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/system/cc-system-panel.ui"); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, titlebar); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, page_title); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, back_button); gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, main_page); gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, main_leaflet); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, remote_desktop_row); + gtk_widget_class_bind_template_child (widget_class, CcSystemPanel, remote_desktop_page); + + gtk_widget_class_bind_template_callback (widget_class, system_panel_update_back_button_cb); + gtk_widget_class_bind_template_callback (widget_class, system_panel_back_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, system_panel_visible_child_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, system_panel_row_activated_cb); + + g_type_ensure (CC_TYPE_SYSTEM_REMOTE_PAGE); } static void @@ -53,4 +144,5 @@ cc_system_panel_init (CcSystemPanel *self) { g_resources_register (cc_system_get_resource ()); gtk_widget_init_template (GTK_WIDGET (self)); + system_panel_visible_child_changed_cb (self); } diff --git a/panels/system/cc-system-panel.ui b/panels/system/cc-system-panel.ui index 10865afca02fe196adb45f756320199e4b4543ac..91e3609cab12e1075b69d898a8f1fac2f21e56c3 100644 --- a/panels/system/cc-system-panel.ui +++ b/panels/system/cc-system-panel.ui @@ -1,14 +1,13 @@ - + \ No newline at end of file diff --git a/panels/system/cc-system-remote-desktop-page.c b/panels/system/cc-system-remote-desktop-page.c new file mode 100644 index 0000000000000000000000000000000000000000..8307cc0ad9c57dff0a966586149b4b7a6c014422 --- /dev/null +++ b/panels/system/cc-system-remote-desktop-page.c @@ -0,0 +1,735 @@ +/* + * cc-system-remote-desktop-page.c + * + * Copyright 2023 Gotam Gorabh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-system-remote-desktop-page" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "cc-list-row.h" + +#include "cc-system-remote-desktop-page.h" +#include "cc-tls-certificate.h" +#include "cc-gnome-remote-desktop.h" +#include "cc-systemd-service.h" +#include "org.gnome.SettingsDaemon.Sharing.h" +#include "cc-list-row.h" + + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif +#include + +#define GCR_API_SUBJECT_TO_CHANGE +#include + +#include + +#include + +#include +#include + +#define GNOME_REMOTE_DESKTOP_SCHEMA_ID "org.gnome.desktop.remote-desktop" +#define GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID "org.gnome.desktop.remote-desktop.rdp" +#define REMOTE_DESKTOP_STORE_CREDENTIALS_TIMEOUT_S 1 +#define REMOTE_DESKTOP_SERVICE "gnome-remote-desktop.service" + +struct _CcSystemRemoteDesktopPage { + AdwPreferencesPage parent_instance; + + GtkWidget *remote_control_switch; + GtkWidget *remote_desktop_toast_overlay; + GtkWidget *remote_desktop_password_entry; + GtkWidget *remote_desktop_password_copy; + GtkWidget *remote_desktop_username_entry; + GtkWidget *remote_desktop_username_copy; + GtkWidget *remote_desktop_device_name_label; + GtkWidget *remote_desktop_device_name_copy; + GtkWidget *remote_desktop_address_label; + GtkWidget *remote_desktop_address_copy; + GtkWidget *remote_desktop_switch; + GtkWidget *remote_desktop_verify_encryption; + GtkWidget *remote_desktop_fingerprint_dialog; + GtkWidget *remote_desktop_fingerprint_left; + GtkWidget *remote_desktop_fingerprint_right; + + GDBusProxy *sharing_proxy; + + guint remote_desktop_name_watch; + guint remote_desktop_store_credentials_id; + GTlsCertificate *remote_desktop_certificate; +}; + +G_DEFINE_TYPE (CcSystemRemoteDesktopPage, cc_system_remote_desktop_page, ADW_TYPE_PREFERENCES_PAGE) + +static void +remote_desktop_show_encryption_fingerprint (CcSystemRemoteDesktopPage *self) +{ + g_autoptr(GByteArray) der = NULL; + g_autoptr(GcrCertificate) gcr_cert = NULL; + g_autofree char *fingerprint = NULL; + g_auto(GStrv) fingerprintv = NULL; + g_autofree char *left_string = NULL; + g_autofree char *right_string = NULL; + + g_return_if_fail (self->remote_desktop_certificate); + + g_object_get (self->remote_desktop_certificate, + "certificate", &der, NULL); + gcr_cert = gcr_simple_certificate_new (der->data, der->len); + if (!gcr_cert) + { + g_warning ("Failed to load GCR TLS certificate representation"); + return; + } + + fingerprint = gcr_certificate_get_fingerprint_hex (gcr_cert, G_CHECKSUM_SHA256); + + fingerprintv = g_strsplit (fingerprint, " ", -1); + g_return_if_fail (g_strv_length (fingerprintv) == 32); + + left_string = g_strdup_printf ( + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n", + fingerprintv[0], fingerprintv[1], fingerprintv[2], fingerprintv[3], + fingerprintv[8], fingerprintv[9], fingerprintv[10], fingerprintv[11], + fingerprintv[16], fingerprintv[17], fingerprintv[18], fingerprintv[19], + fingerprintv[24], fingerprintv[25], fingerprintv[26], fingerprintv[27]); + + right_string = g_strdup_printf ( + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n" + "%s:%s:%s:%s\n", + fingerprintv[4], fingerprintv[5], fingerprintv[6], fingerprintv[7], + fingerprintv[12], fingerprintv[13], fingerprintv[14], fingerprintv[15], + fingerprintv[20], fingerprintv[21], fingerprintv[22], fingerprintv[23], + fingerprintv[28], fingerprintv[29], fingerprintv[30], fingerprintv[31]); + + gtk_label_set_label (GTK_LABEL (self->remote_desktop_fingerprint_left), + left_string); + gtk_label_set_label (GTK_LABEL (self->remote_desktop_fingerprint_right), + right_string); + + gtk_window_present (GTK_WINDOW (self->remote_desktop_fingerprint_dialog)); +} + +GCancellable * +cc_system_remote_desktop_page_get_cancellable (CcSystemRemoteDesktopPage *self) +{ + g_return_val_if_fail(self, NULL); + + GCancellable *cancellable = g_cancellable_new(); + return cancellable; +} + +static char * +get_hostname (void) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) res = NULL; + g_autoptr(GVariant) inner = NULL; + g_autoptr(GError) error = NULL; + const char *hostname; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) + { + g_warning ("Failed to get system bus connection: %s", error->message); + return NULL; + } + res = g_dbus_connection_call_sync (bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.hostname1", + "PrettyHostname"), + (GVariantType*)"(v)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (res == NULL) + { + g_warning ("Getting pretty hostname failed: %s", error->message); + return NULL; + } + + g_variant_get (res, "(v)", &inner); + hostname = g_variant_get_string (inner, NULL); + if (g_strcmp0 (hostname, "") != 0) + return g_strdup (hostname); + + g_clear_pointer (&inner, g_variant_unref); + g_clear_pointer (&res, g_variant_unref); + + res = g_dbus_connection_call_sync (bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.hostname1", + "Hostname"), + (GVariantType*)"(v)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (res == NULL) + { + g_warning ("Getting hostname failed: %s", error->message); + return NULL; + } + + g_variant_get (res, "(v)", &inner); + return g_variant_dup_string (inner, NULL); +} + +static void +cc_system_remote_desktop_page_setup_label_with_hostname (CcSystemRemoteDesktopPage *self, + GtkWidget *label) +{ + g_autofree gchar *text = NULL; + const gchar *hostname; + + hostname = get_hostname (); + + if (label == self->remote_desktop_address_label) + { + text = g_strdup_printf ("ms-rd://%s", hostname); + } + else + g_assert_not_reached (); + + gtk_label_set_label (GTK_LABEL (label), text); +} + +static gboolean +cc_system_remote_desktop_page_check_schema_available (CcSystemRemoteDesktopPage *self, + const gchar *schema_id) +{ + GSettingsSchemaSource *source; + g_autoptr(GSettingsSchema) schema = NULL; + + source = g_settings_schema_source_get_default (); + if (!source) + return FALSE; + + schema = g_settings_schema_source_lookup (source, schema_id, TRUE); + if (!schema) + return FALSE; + + return TRUE; +} + +static gboolean +store_remote_desktop_credentials_timeout (gpointer user_data) +{ + CcSystemRemoteDesktopPage *self = (CcSystemRemoteDesktopPage *)user_data; + const char *username, *password; + + username = gtk_editable_get_text (GTK_EDITABLE (self->remote_desktop_username_entry)); + password = gtk_editable_get_text (GTK_EDITABLE (self->remote_desktop_password_entry)); + + if (username && password) + { + cc_grd_store_rdp_credentials (username, password, + cc_system_remote_desktop_page_get_cancellable (self)); + } + + self->remote_desktop_store_credentials_id = 0; + + return G_SOURCE_REMOVE; +} + +static gboolean +is_remote_desktop_enabled (CcSystemRemoteDesktopPage *self) +{ + g_autoptr(GSettings) rdp_settings = NULL; + + rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID); + + if (!g_settings_get_boolean (rdp_settings, "enable")) + return FALSE; + + return cc_is_service_active (REMOTE_DESKTOP_SERVICE, G_BUS_TYPE_SESSION); +} + +static void +enable_gnome_remote_desktop_service (CcSystemRemoteDesktopPage *self) +{ + g_autoptr(GError) error = NULL; + + if (is_remote_desktop_enabled (self)) + return; + + if (!cc_enable_service (REMOTE_DESKTOP_SERVICE, + G_BUS_TYPE_SESSION, + &error)) + g_warning ("Failed to enable remote desktop service: %s", error->message); +} + +static void +remote_desktop_credentials_changed (CcSystemRemoteDesktopPage *self) +{ + g_clear_handle_id (&self->remote_desktop_store_credentials_id, + g_source_remove); + + self->remote_desktop_store_credentials_id = + g_timeout_add_seconds (REMOTE_DESKTOP_STORE_CREDENTIALS_TIMEOUT_S, + store_remote_desktop_credentials_timeout, + self); +} + +static void +calc_default_tls_paths (char **out_dir_path, + char **out_cert_path, + char **out_key_path) +{ + g_autofree char *dir_path = NULL; + + dir_path = g_strdup_printf ("%s/gnome-remote-desktop", + g_get_user_data_dir ()); + + if (out_cert_path) + *out_cert_path = g_strdup_printf ("%s/rdp-tls.crt", dir_path); + if (out_key_path) + *out_key_path = g_strdup_printf ("%s/rdp-tls.key", dir_path); + + if (out_dir_path) + *out_dir_path = g_steal_pointer (&dir_path); +} + +static void +set_tls_certificate (CcSystemRemoteDesktopPage *self, + GTlsCertificate *tls_certificate) +{ + g_set_object (&self->remote_desktop_certificate, + tls_certificate); + gtk_widget_set_sensitive (self->remote_desktop_verify_encryption, TRUE); +} + +static void +on_certificate_generated (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CcSystemRemoteDesktopPage *self; + g_autoptr(GTlsCertificate) tls_certificate = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *cert_path = NULL; + g_autofree char *key_path = NULL; + g_autoptr(GSettings) rdp_settings = NULL; + + tls_certificate = bonsai_tls_certificate_new_generate_finish (res, &error); + if (!tls_certificate) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_warning ("Failed to generate TLS certificate: %s", error->message); + return; + } + + self = (CcSystemRemoteDesktopPage *)user_data; + + calc_default_tls_paths (NULL, &cert_path, &key_path); + + rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID); + + g_settings_set_string (rdp_settings, "tls-cert", cert_path); + g_settings_set_string (rdp_settings, "tls-key", key_path); + + set_tls_certificate (self, tls_certificate); + + enable_gnome_remote_desktop_service (self); +} + +static void +disable_gnome_remote_desktop_service (CcSystemRemoteDesktopPage *self) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GSettings) rdp_settings = NULL; + + rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID); + + g_settings_set_boolean (rdp_settings, "enable", FALSE); + + if (!cc_disable_service (REMOTE_DESKTOP_SERVICE, + G_BUS_TYPE_SESSION, + &error)) + g_warning ("Failed to enable remote desktop service: %s", error->message); +} + +static void +enable_gnome_remote_desktop (CcSystemRemoteDesktopPage *self) +{ + g_autofree char *dir_path = NULL; + g_autofree char *cert_path = NULL; + g_autofree char *key_path = NULL; + g_autoptr(GFile) dir = NULL; + g_autoptr(GFile) cert_file = NULL; + g_autoptr(GFile) key_file = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GSettings) rdp_settings = NULL; + + rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID); + + g_settings_set_boolean (rdp_settings, "enable", TRUE); + + cert_path = g_settings_get_string (rdp_settings, "tls-cert"); + key_path = g_settings_get_string (rdp_settings, "tls-key"); + if (strlen (cert_path) > 0 && + strlen (key_path) > 0) + { + g_autoptr(GTlsCertificate) tls_certificate = NULL; + + tls_certificate = g_tls_certificate_new_from_file (cert_path, &error); + if (tls_certificate) + { + set_tls_certificate (self, tls_certificate); + + enable_gnome_remote_desktop_service (self); + return; + } + + g_warning ("Configured TLS certificate invalid: %s", error->message); + return; + } + + calc_default_tls_paths (&dir_path, &cert_path, &key_path); + + dir = g_file_new_for_path (dir_path); + if (!g_file_query_exists (dir, NULL)) + { + if (!g_file_make_directory_with_parents (dir, NULL, &error)) + { + g_warning ("Failed to create remote desktop certificate directory: %s", + error->message); + return; + } + } + + cert_file = g_file_new_for_path (cert_path); + key_file = g_file_new_for_path (key_path); + + if (g_file_query_exists (cert_file, NULL) && + g_file_query_exists (key_file, NULL)) + { + g_autoptr(GTlsCertificate) tls_certificate = NULL; + + tls_certificate = g_tls_certificate_new_from_file (cert_path, &error); + if (tls_certificate) + { + g_settings_set_string (rdp_settings, "tls-cert", cert_path); + g_settings_set_string (rdp_settings, "tls-key", key_path); + + set_tls_certificate (self, tls_certificate); + + enable_gnome_remote_desktop_service (self); + return; + } + + g_warning ("Existing TLS certificate invalid: %s", error->message); + return; + } + + bonsai_tls_certificate_new_generate_async (cert_path, + key_path, + "US", + "GNOME", + cc_system_remote_desktop_page_get_cancellable (self), + on_certificate_generated, + self); +} + +static void +on_remote_desktop_active_changed (GtkWidget *widget, + GParamSpec *pspec, + CcSystemRemoteDesktopPage *self) +{ + if (gtk_switch_get_active (GTK_SWITCH (widget))) + enable_gnome_remote_desktop (self); + else + disable_gnome_remote_desktop_service (self); +} + + + +static void +add_toast (CcSystemRemoteDesktopPage *self, + const char *message) +{ + adw_toast_overlay_add_toast (ADW_TOAST_OVERLAY (self->remote_desktop_toast_overlay), + adw_toast_new (message)); +} + +static void +on_device_name_copy_clicked (GtkButton *button, + CcSystemRemoteDesktopPage *self) +{ + GtkLabel *label = GTK_LABEL (self->remote_desktop_device_name_label); + + gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)), + gtk_label_get_text (label)); + add_toast (self, _("Device name copied")); +} + +static void +on_device_address_copy_clicked (GtkButton *button, + CcSystemRemoteDesktopPage *self) +{ + GtkLabel *label = GTK_LABEL (self->remote_desktop_address_label); + + gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)), + gtk_label_get_text (label)); + add_toast (self, _("Device address copied")); +} + +static void +on_username_copy_clicked (GtkButton *button, + CcSystemRemoteDesktopPage *self) +{ + GtkEditable *editable = GTK_EDITABLE (self->remote_desktop_username_entry); + + gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)), + gtk_editable_get_text (editable)); + add_toast (self, _("Username copied")); +} + +static void +on_password_copy_clicked (GtkButton *button, + CcSystemRemoteDesktopPage *self) +{ + GtkEditable *editable = GTK_EDITABLE (self->remote_desktop_password_entry); + + gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)), + gtk_editable_get_text (editable)); + add_toast (self, _("Password copied")); +} + +static void +cc_system_remote_desktop_page_setup_remote_desktop_dialog (CcSystemRemoteDesktopPage *self) +{ + const gchar *username = NULL; + const gchar *password = NULL; + g_autoptr(GSettings) rdp_settings = NULL; + g_autofree char *hostname = NULL; + + rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID); + + g_settings_bind (rdp_settings, + "view-only", + self->remote_control_switch, + "active", + G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_INVERT_BOOLEAN); + g_object_bind_property (self->remote_desktop_switch, "active", + self->remote_control_switch, "sensitive", + G_BINDING_SYNC_CREATE); + + hostname = get_hostname (); + gtk_label_set_label (GTK_LABEL (self->remote_desktop_device_name_label), + hostname); + + username = cc_grd_lookup_rdp_username (cc_system_remote_desktop_page_get_cancellable (self)); + password = cc_grd_lookup_rdp_password (cc_system_remote_desktop_page_get_cancellable (self)); + if (username != NULL) + gtk_editable_set_text (GTK_EDITABLE (self->remote_desktop_username_entry), username); + if (password != NULL) + gtk_editable_set_text (GTK_EDITABLE (self->remote_desktop_password_entry), password); + + g_signal_connect_swapped (self->remote_desktop_username_entry, + "notify::text", + G_CALLBACK (remote_desktop_credentials_changed), + self); + g_signal_connect_swapped (self->remote_desktop_password_entry, + "notify::text", + G_CALLBACK (remote_desktop_credentials_changed), + self); + if (username == NULL) + { + struct passwd *pw = getpwuid (getuid ()); + if (pw != NULL) + { + gtk_editable_set_text (GTK_EDITABLE (self->remote_desktop_username_entry), + pw->pw_name); + } + else + { + g_warning ("Failed to get username: %s", g_strerror (errno)); + } + } + + if (password == NULL) + { + char * pw = pw_generate (); + if (pw) + gtk_editable_set_text (GTK_EDITABLE (self->remote_desktop_password_entry), + pw ); + } + g_signal_connect (self->remote_desktop_device_name_copy, + "clicked", G_CALLBACK (on_device_name_copy_clicked), + self); + g_signal_connect (self->remote_desktop_address_copy, + "clicked", G_CALLBACK (on_device_address_copy_clicked), + self); + g_signal_connect (self->remote_desktop_username_copy, + "clicked", G_CALLBACK (on_username_copy_clicked), + self); + g_signal_connect (self->remote_desktop_password_copy, + "clicked", G_CALLBACK (on_password_copy_clicked), + self); + + g_signal_connect (self->remote_desktop_switch, "notify::active", + G_CALLBACK (on_remote_desktop_active_changed), self); + + if (is_remote_desktop_enabled (self)) + { + gtk_switch_set_active (GTK_SWITCH (self->remote_desktop_switch), + TRUE); + } +} + +static void +remote_desktop_name_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + CcSystemRemoteDesktopPage *self = (CcSystemRemoteDesktopPage *)user_data; + + g_bus_unwatch_name (self->remote_desktop_name_watch); + self->remote_desktop_name_watch = 0; + + cc_system_remote_desktop_page_setup_remote_desktop_dialog (self); +} + +static void +check_remote_desktop_available (CcSystemRemoteDesktopPage *self) +{ + if (!cc_system_remote_desktop_page_check_schema_available (self, GNOME_REMOTE_DESKTOP_SCHEMA_ID)) + return; + + if (!cc_system_remote_desktop_page_check_schema_available (self, GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID)) + return; + + self->remote_desktop_name_watch = g_bus_watch_name (G_BUS_TYPE_SESSION, + "org.gnome.Mutter.RemoteDesktop", + G_BUS_NAME_WATCHER_FLAGS_NONE, + remote_desktop_name_appeared, + NULL, + self, + NULL); +} + +static void +sharing_proxy_ready (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + CcSystemRemoteDesktopPage *self; + GDBusProxy *proxy; + GtkWidget *parent; + g_autoptr(GError) error = NULL; + + proxy = G_DBUS_PROXY (gsd_sharing_proxy_new_for_bus_finish (res, &error)); + if (!proxy) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to get sharing proxy: %s", error->message); + return; + } + + self = (CcSystemRemoteDesktopPage *)user_data; + self->sharing_proxy = proxy; + + /* screen sharing */ + check_remote_desktop_available (self); + + cc_system_remote_desktop_page_setup_label_with_hostname (self, self->remote_desktop_address_label); +} + +static void +cc_system_remote_desktop_page_class_init (CcSystemRemoteDesktopPageClass * klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/system/cc-system-remote-desktop-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_toast_overlay); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_switch); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_control_switch); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_username_entry); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_username_copy); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_password_entry); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_password_copy); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_device_name_label); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_device_name_copy); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_address_label); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_address_copy); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_verify_encryption); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_fingerprint_dialog); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_fingerprint_left); + gtk_widget_class_bind_template_child (widget_class, CcSystemRemoteDesktopPage, remote_desktop_fingerprint_right); + + gtk_widget_class_bind_template_callback (widget_class, remote_desktop_show_encryption_fingerprint); +} + +static void +cc_system_remote_desktop_page_init (CcSystemRemoteDesktopPage *self) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gsd_sharing_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.gnome.SettingsDaemon.Sharing", + "/org/gnome/SettingsDaemon/Sharing", + cc_system_remote_desktop_page_get_cancellable(self), + sharing_proxy_ready, + self); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, + "/org/gnome/control-center/system/remote-desktop.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} diff --git a/panels/system/cc-system-remote-desktop-page.h b/panels/system/cc-system-remote-desktop-page.h new file mode 100644 index 0000000000000000000000000000000000000000..0fb4aae01fe21fb7235dcc7c1a79a096a55ccf7b --- /dev/null +++ b/panels/system/cc-system-remote-desktop-page.h @@ -0,0 +1,32 @@ +/* + * cc-system-remote-page.h + * + * Copyright 2023 Gotam Gorabh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_SYSTEM_REMOTE_PAGE (cc_system_remote_desktop_page_get_type ()) + +G_DECLARE_FINAL_TYPE (CcSystemRemoteDesktopPage, cc_system_remote_desktop_page, CC, CC_TYPE_SYSTEM_REMOTE_DESKTOP_PAGE, AdwPreferencesPage) + +G_END_DECLS \ No newline at end of file diff --git a/panels/system/cc-system-remote-desktop-page.ui b/panels/system/cc-system-remote-desktop-page.ui new file mode 100644 index 0000000000000000000000000000000000000000..1cfdeaff584bcfd2a459794436daf19de4a46759 --- /dev/null +++ b/panels/system/cc-system-remote-desktop-page.ui @@ -0,0 +1,214 @@ + + + + + + 360 + 360 + True + True + + + + vertical + + + end + end + + + + + Encryption Fingerprint + The encryption fingerprint can be seen in connecting clients and should be identical. + True + + + horizontal + center + + + + + + + + + + + + + + + + + + + + diff --git a/panels/system/cc-systemd-service.c b/panels/system/cc-systemd-service.c new file mode 100644 index 0000000000000000000000000000000000000000..d2b6346766bfbe1498a9c614490407cd30ddd839 --- /dev/null +++ b/panels/system/cc-systemd-service.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 Intel, Inc + * Copyright (C) 2022 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Author: Thomas Wood + * + */ + +#include "cc-systemd-service.h" + +gboolean +cc_is_service_active (const char *service, + GBusType bus_type) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) unit_path_variant = NULL; + g_autofree char *unit_path = NULL; + g_autoptr(GVariant) active_state_prop = NULL; + g_autoptr(GVariant) active_state_variant = NULL; + const char *active_state = NULL; + g_autoptr(GVariant) unit_state_prop = NULL; + g_autoptr(GVariant) unit_state_variant = NULL; + const char *unit_state = NULL; + + connection = g_bus_get_sync (bus_type, NULL, &error); + if (!connection) + { + g_warning ("Failed connecting to D-Bus system bus: %s", error->message); + return FALSE; + } + + unit_path_variant = + g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + g_variant_new ("(s)", + service), + (GVariantType *) "(o)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL); + + if (!unit_path_variant) + return FALSE; + g_variant_get_child (unit_path_variant, 0, "o", &unit_path); + + active_state_prop = + g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.systemd1.Unit", + "ActiveState"), + (GVariantType *) "(v)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (!active_state_prop) + { + g_warning ("Failed to get service active state: %s", error->message); + return FALSE; + } + g_variant_get_child (active_state_prop, 0, "v", &active_state_variant); + active_state = g_variant_get_string (active_state_variant, NULL); + + if (g_strcmp0 (active_state, "active") != 0 && + g_strcmp0 (active_state, "activating") != 0) + return FALSE; + + unit_state_prop = + g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.systemd1.Unit", + "UnitFileState"), + (GVariantType *) "(v)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (!unit_state_prop) + { + g_warning ("Failed to get service active state: %s", error->message); + return FALSE; + } + g_variant_get_child (unit_state_prop, 0, "v", &unit_state_variant); + unit_state = g_variant_get_string (unit_state_variant, NULL); + + if (g_strcmp0 (unit_state, "enabled") == 0 || + g_strcmp0 (unit_state, "static") == 0) + return TRUE; + else + return FALSE; +} + +gboolean +cc_enable_service (const char *service, + GBusType bus_type, + GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) start_result = NULL; + g_autoptr(GVariant) enable_result = NULL; + const char *service_list[] = { service, NULL }; + + connection = g_bus_get_sync (bus_type, NULL, error); + if (!connection) + { + g_prefix_error_literal (error, "Failed connecting to D-Bus system bus: "); + return FALSE; + } + + start_result = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + g_variant_new ("(ss)", + service, + "replace"), + (GVariantType *) "(o)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (!start_result) + { + g_prefix_error_literal (error, "Failed to start service: "); + return FALSE; + } + + enable_result = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "EnableUnitFiles", + g_variant_new ("(^asbb)", + service_list, + FALSE, FALSE), + (GVariantType *) "(ba(sss))", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (!enable_result) + { + g_prefix_error_literal (error, "Failed to enable service: "); + return FALSE; + } + + return TRUE; +} + +gboolean +cc_disable_service (const char *service, + GBusType bus_type, + GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) stop_result = NULL; + g_autoptr(GVariant) disable_result = NULL; + const char *service_list[] = { service, NULL }; + + connection = g_bus_get_sync (bus_type, NULL, error); + if (!connection) + { + g_prefix_error_literal (error, "Failed connecting to D-Bus system bus: "); + return FALSE; + } + + stop_result = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + g_variant_new ("(ss)", service, "replace"), + (GVariantType *) "(o)", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + if (!stop_result) + { + g_prefix_error_literal (error, "Failed to stop service: "); + return FALSE; + } + + disable_result = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "DisableUnitFiles", + g_variant_new ("(^asb)", service_list, FALSE, + FALSE), + (GVariantType *) "(a(sss))", + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (!stop_result) + { + g_prefix_error_literal (error, "Failed to disable service: "); + return FALSE; + } + + return TRUE; +} diff --git a/panels/system/cc-systemd-service.h b/panels/system/cc-systemd-service.h new file mode 100644 index 0000000000000000000000000000000000000000..89b1d9a67e1642b81f03c42af19cf3f01388cafb --- /dev/null +++ b/panels/system/cc-systemd-service.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#pragma once + +#include + +gboolean cc_is_service_active (const char *service, + GBusType bus_type); + +gboolean cc_enable_service (const char *service, + GBusType bus_type, + GError **error); + +gboolean cc_disable_service (const char *service, + GBusType bus_type, + GError **error); diff --git a/panels/system/cc-tls-certificate.c b/panels/system/cc-tls-certificate.c new file mode 100644 index 0000000000000000000000000000000000000000..3b616f86b271b642d4b43136a0d7636950e3187b --- /dev/null +++ b/panels/system/cc-tls-certificate.c @@ -0,0 +1,511 @@ +/* cc-tls-certificate.c + * + * Copyright 2018 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "cc-tls-certificate.h" + +#define DEFAULT_KEY_SIZE 4096 +#define DEFAULT_EXPIRATION (60L*60L*24L*2L*365L) + +static void +_gnutls_datum_clear (gnutls_datum_t *datum) +{ + if (datum->data != NULL) + gnutls_free (datum->data); +} + +static void +_gnutls_crt_free (gnutls_x509_crt_t *cert) +{ + if (cert != NULL) + gnutls_x509_crt_deinit (*cert); +} + +static void +_gnutls_privkey_free (gnutls_x509_privkey_t *privkey) +{ + if (privkey != NULL) + gnutls_x509_privkey_deinit (*privkey); +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (gnutls_datum_t, _gnutls_datum_clear) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (gnutls_x509_crt_t, _gnutls_crt_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (gnutls_x509_privkey_t, _gnutls_privkey_free) + +typedef struct +{ + gchar *public_key_path; + gchar *private_key_path; + gchar *c; + gchar *cn; +} GenerateData; + +static void +generate_data_free (GenerateData *data) +{ + g_clear_pointer (&data->public_key_path, g_free); + g_clear_pointer (&data->private_key_path, g_free); + g_clear_pointer (&data->c, g_free); + g_clear_pointer (&data->cn, g_free); + g_slice_free (GenerateData, data); +} + +static gboolean +make_directory_parent (const gchar *path, + GError **error) +{ + g_autofree gchar *dir = NULL; + + g_assert (path != NULL); + g_assert (error != NULL); + + dir = g_path_get_dirname (path); + + if (g_mkdir_with_parents (dir, 0750) == -1) + { + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static void +bonsai_tls_certificate_generate_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GenerateData *data = task_data; + g_autoptr(GTlsCertificate) certificate = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(gnutls_x509_crt_t) certptr = NULL; + g_autoptr(gnutls_x509_privkey_t) privkeyptr = NULL; + g_auto(gnutls_datum_t) pubkey_data = {0}; + g_auto(gnutls_datum_t) privkey_data = {0}; + g_autofree char *dn = NULL; + gnutls_x509_privkey_t privkey; + gnutls_x509_crt_t cert; + guint32 serial = 1; + int gtlsret = 0; + + g_assert (G_IS_TASK (task)); + g_assert (source_object == NULL); + g_assert (data != NULL); + g_assert (data->public_key_path != NULL); + g_assert (data->private_key_path != NULL); + g_assert (data->c != NULL); + g_assert (data->cn != NULL); + + if (!make_directory_parent (data->public_key_path, &error) || + !make_directory_parent (data->private_key_path, &error)) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + /* + * From the GnuTLS documentation: + * + * To be consistent with the X.509/PKIX specifications the provided serial + * should be a big-endian positive number (i.e. it's leftmost bit should be + * zero). + */ + serial = GUINT32_TO_BE (serial); + +#define HANDLE_FAILURE(x) \ + G_STMT_START { \ + gtlsret = x; \ + if (gtlsret != GNUTLS_E_SUCCESS) \ + goto failure; \ + } G_STMT_END + + HANDLE_FAILURE (gnutls_x509_crt_init (&cert)); + certptr = &cert; + HANDLE_FAILURE (gnutls_x509_crt_set_version (cert, 3)); + HANDLE_FAILURE (gnutls_x509_crt_set_activation_time (cert, time (NULL))); + dn = g_strdup_printf ("C=%s,CN=%s", data->c, data->cn); + HANDLE_FAILURE (gnutls_x509_crt_set_dn (cert, dn, NULL)); + HANDLE_FAILURE (gnutls_x509_crt_set_serial (cert, &serial, sizeof serial)); + /* 5 years. We'll figure out key rotation in that time... */ + HANDLE_FAILURE (gnutls_x509_crt_set_expiration_time (cert, time (NULL) + DEFAULT_EXPIRATION)); + HANDLE_FAILURE (gnutls_x509_privkey_init (&privkey)); + privkeyptr = &privkey; + HANDLE_FAILURE (gnutls_x509_privkey_generate (privkey, GNUTLS_PK_RSA, DEFAULT_KEY_SIZE, 0)); + HANDLE_FAILURE (gnutls_x509_crt_set_key (cert, privkey)); + HANDLE_FAILURE (gnutls_x509_crt_sign (cert, cert, privkey)); + HANDLE_FAILURE (gnutls_x509_crt_export2 (cert, GNUTLS_X509_FMT_PEM, &pubkey_data)); + if (!g_file_set_contents (data->public_key_path, (char *)pubkey_data.data, pubkey_data.size, &error)) + goto failure; + + HANDLE_FAILURE (gnutls_x509_privkey_export2 (privkey, GNUTLS_X509_FMT_PEM, &privkey_data)); + if (!g_file_set_contents (data->private_key_path, (char*)privkey_data.data, privkey_data.size, &error)) + goto failure; + +#undef HANDLE_FAILURE + + if ((certificate = g_tls_certificate_new_from_files (data->public_key_path, data->private_key_path, &error))) + { + g_task_return_pointer (task, g_steal_pointer (&certificate), g_object_unref); + return; + } + +failure: + + if (error != NULL) + g_task_return_error (task, g_steal_pointer (&error)); + else if (gtlsret != 0) + g_task_return_new_error (task, + G_TLS_ERROR, + G_TLS_ERROR_MISC, + "GnuTLS Error: %s", + gnutls_strerror (gtlsret)); + else + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Failed to generate TLS certificate pair"); +} + +void +bonsai_tls_certificate_new_generate_async (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + GenerateData *data; + + g_return_if_fail (public_key_path != NULL); + g_return_if_fail (private_key_path != NULL); + g_return_if_fail (c != NULL); + g_return_if_fail (cn != NULL); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, bonsai_tls_certificate_new_generate_async); + + data = g_slice_new0 (GenerateData); + data->public_key_path = g_strdup (public_key_path); + data->private_key_path = g_strdup (private_key_path); + data->c = g_strdup (c); + data->cn = g_strdup (cn); + g_task_set_task_data (task, data, (GDestroyNotify)generate_data_free); + + g_task_run_in_thread (task, bonsai_tls_certificate_generate_worker); +} + +GTlsCertificate * +bonsai_tls_certificate_new_generate_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +GTlsCertificate * +bonsai_tls_certificate_new_generate (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GTask) task = NULL; + GenerateData *data; + + g_return_val_if_fail (public_key_path != NULL, NULL); + g_return_val_if_fail (private_key_path != NULL, NULL); + g_return_val_if_fail (c != NULL, NULL); + g_return_val_if_fail (cn != NULL, NULL); + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + + task = g_task_new (NULL, cancellable, NULL, NULL); + g_task_set_source_tag (task, bonsai_tls_certificate_new_generate); + + data = g_slice_new0 (GenerateData); + data->public_key_path = g_strdup (public_key_path); + data->private_key_path = g_strdup (private_key_path); + data->c = g_strdup (c); + data->cn = g_strdup (cn); + g_task_set_task_data (task, data, (GDestroyNotify)generate_data_free); + + bonsai_tls_certificate_generate_worker (task, NULL, data, cancellable); + + return g_task_propagate_pointer (task, error); +} + +gchar * +bonsai_tls_certificate_get_hash (GTlsCertificate *cert) +{ + g_autoptr(GByteArray) bytesarray = NULL; + g_autoptr(GChecksum) checksum = NULL; + + g_return_val_if_fail (G_IS_TLS_CERTIFICATE (cert), NULL); + + g_object_get (cert, "certificate", &bytesarray, NULL); + + checksum = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (checksum, bytesarray->data, bytesarray->len); + + return g_ascii_strdown (g_checksum_get_string (checksum), -1); +} + +typedef struct +{ + gchar *public_key_path; + gchar *private_key_path; + gchar *c; + gchar *cn; +} NewFromFilesOrGenerate; + +static void +new_from_files_or_generate_free (gpointer data) +{ + NewFromFilesOrGenerate *state = data; + + g_clear_pointer (&state->public_key_path, g_free); + g_clear_pointer (&state->private_key_path, g_free); + g_clear_pointer (&state->c, g_free); + g_clear_pointer (&state->cn, g_free); + g_free (state); +} + +static void +bonsai_tls_certificate_new_from_files_or_generate_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NewFromFilesOrGenerate *state = task_data; + g_autoptr(GTlsCertificate) certificate = NULL; + g_autoptr(GError) error = NULL; + + g_assert (G_IS_TASK (task)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_assert (state != NULL); + g_assert (state->public_key_path != NULL); + g_assert (state->private_key_path != NULL); + + /* Generate new public/private key for server if we need one. + * Ideally, we would generate something signed by a real CA + * for the user. But since this is "private cloud" oriented, + * we should be fine for now. + */ + if (!g_file_test (state->public_key_path, G_FILE_TEST_EXISTS) || + !g_file_test (state->private_key_path, G_FILE_TEST_EXISTS)) + certificate = bonsai_tls_certificate_new_generate (state->public_key_path, + state->private_key_path, + state->c, + state->cn, + cancellable, + &error); + else + certificate = g_tls_certificate_new_from_files (state->public_key_path, + state->private_key_path, + &error); + + if (certificate == NULL) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, g_steal_pointer (&certificate), g_object_unref); +} + +/** + * bonsai_tls_certificate_new_from_files_or_generate_async: + * @public_key_path: the path to the public key file + * @private_key_path: the path to the private key file + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: a callback to execute upon completion + * @user_data: closure data for @callback + * + * Asynchronously requests that a certificate is loaded, or generate one if it + * does not yet exist. The generated certificate is a self-signed certificate. + * + * Since: 0.2 + */ +void +bonsai_tls_certificate_new_from_files_or_generate_async (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + NewFromFilesOrGenerate state; + + g_assert (public_key_path != NULL); + g_assert (private_key_path != NULL); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + state.public_key_path = g_strdup (public_key_path); + state.private_key_path = g_strdup (private_key_path); + state.c = g_strdup (c); + state.cn = g_strdup (cn); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, bonsai_tls_certificate_new_from_files_or_generate_async); + g_task_set_task_data (task, g_memdup2 (&state, sizeof state), new_from_files_or_generate_free); + g_task_run_in_thread (task, bonsai_tls_certificate_new_from_files_or_generate_worker); +} + +/** + * bonsai_tls_certificate_new_from_files_or_generate_finish: + * @result: a #GAsyncResult provided to callback + * @error: a location for a #GError, or %NULL + * + * Completes a request to + * bonsai_tls_certificate_new_from_files_or_generate_async() which will + * either load a #GTlsCertificate for the files if they exist, or generate + * a new self-signed certificate in their place. + * + * Returns: (transfer none): a #GTlsCertificate or %NULL and @error is set. + * + * Since: 0.2 + */ +GTlsCertificate * +bonsai_tls_certificate_new_from_files_or_generate_finish (GAsyncResult *result, + GError **error) +{ + g_assert (G_IS_TASK (result)); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * bonsai_tls_certificate_new_from_files_or_generate: + * @public_key_path: the path to the public key + * @private_key_path: the path to the private key + * @c: the C for the certificate + * @cn: the CN for the certificate + * @cancellable: (nullable): a #GCancellable or %NULL + * @error: the location for the error + * + * Loads a certificate or generates a new self-signed certificate in + * it's place. + * + * Returns: (transfer full): a #GTlsCertificate or %NULL and @error is set + * + * Since: 0.2 + */ +GTlsCertificate * +bonsai_tls_certificate_new_from_files_or_generate (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GError **error) +{ + GTlsCertificate *ret; + + g_return_val_if_fail (public_key_path != NULL, NULL); + g_return_val_if_fail (private_key_path != NULL, NULL); + g_return_val_if_fail (c != NULL, NULL); + g_return_val_if_fail (cn != NULL, NULL); + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + + if (!(ret = g_tls_certificate_new_from_files (public_key_path, private_key_path, NULL))) + ret = bonsai_tls_certificate_new_generate (public_key_path, + private_key_path, + c, + cn, + cancellable, + error); + + return g_steal_pointer (&ret); +} + +/** + * bonsai_tls_certificate_new_for_user: + * @public_key_path: the path to the public key + * @private_key_path: the path to the private key + * + * This is a simplified form to create a new certificate or load a previously + * created certificate for the current user. + * + * Returns: (transfer none): a #GTlsCertificate or %NULL and @error is set. + * + * Since: 0.2 + */ +GTlsCertificate * +bonsai_tls_certificate_new_for_user (GCancellable *cancellable, + GError **error) +{ + g_autofree gchar *public_key_path = NULL; + g_autofree gchar *private_key_path = NULL; + + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + + public_key_path = g_build_filename (g_get_user_config_dir (), "bonsai", "public.key", NULL); + private_key_path = g_build_filename (g_get_user_config_dir (), "bonsai", "private.key", NULL); + + return bonsai_tls_certificate_new_from_files_or_generate (public_key_path, + private_key_path, + "US", + "GNOME", + cancellable, + error); +} + +gboolean +bonsai_is_tls_hash (const gchar *hash) +{ + guint len = 0; + + if (hash == NULL) + return FALSE; + + for (; *hash; hash++) + { + if (len == 64) + return FALSE; + + switch (*hash) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + len++; + break; + + default: + return FALSE; + } + } + + return len == 64; +} + diff --git a/panels/system/cc-tls-certificate.h b/panels/system/cc-tls-certificate.h new file mode 100644 index 0000000000000000000000000000000000000000..9b9a8d501d83aebebfde9f1c5c2f8281df9ecc3c --- /dev/null +++ b/panels/system/cc-tls-certificate.h @@ -0,0 +1,63 @@ +/* cc-tls-certificate.h + * + * Copyright 2018 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +gboolean bonsai_is_tls_hash (const gchar *hash); +GTlsCertificate *bonsai_tls_certificate_new_generate (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GError **error); +void bonsai_tls_certificate_new_generate_async (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GTlsCertificate *bonsai_tls_certificate_new_generate_finish (GAsyncResult *result, + GError **error); +gchar *bonsai_tls_certificate_get_hash (GTlsCertificate *cert); +GTlsCertificate *bonsai_tls_certificate_new_from_files_or_generate (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GError **error); +void bonsai_tls_certificate_new_from_files_or_generate_async (const gchar *public_key_path, + const gchar *private_key_path, + const gchar *c, + const gchar *cn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GTlsCertificate *bonsai_tls_certificate_new_from_files_or_generate_finish (GAsyncResult *result, + GError **error); +GTlsCertificate *bonsai_tls_certificate_new_for_user (GCancellable *cancellable, + GError **error); + +G_END_DECLS + diff --git a/panels/system/meson.build b/panels/system/meson.build index fed92cd25ffa36e3bab0bad0c8742930a9b8fa02..78639d9fce4f0e15f62bac98d9d3b80dc852ecc1 100644 --- a/panels/system/meson.build +++ b/panels/system/meson.build @@ -12,6 +12,10 @@ i18n.merge_file( sources = files( 'cc-system-panel.c', + 'cc-system-remote-desktop-page.c', + 'cc-gnome-remote-desktop.c', + 'cc-tls-certificate.c', + 'cc-systemd-service.c', ) sources += gnome.compile_resources( @@ -21,12 +25,30 @@ sources += gnome.compile_resources( export : true ) -deps = common_deps +settings_daemon = 'org.gnome.SettingsDaemon' +gdbus = settings_daemon + '.Sharing' + +sources += gnome.gdbus_codegen( + gdbus, + gdbus + '.xml', + interface_prefix: settings_daemon + '.', + namespace: 'Gsd' +) + + +libsecret_dep = dependency('libsecret-1') +gnutls_dep = dependency('gnutls') panels_libs += static_library( cappletname, sources: sources, include_directories: [ top_inc, common_inc ], - dependencies: deps, + dependencies: [ + common_deps, + libsecret_dep, + gnutls_dep, + gcr_dep, + pwquality_dep, + ], c_args: cflags ) diff --git a/panels/system/org.gnome.SettingsDaemon.Sharing.xml b/panels/system/org.gnome.SettingsDaemon.Sharing.xml new file mode 100644 index 0000000000000000000000000000000000000000..d019aa0d4df814e8b450c0224d3537c618ac1f29 --- /dev/null +++ b/panels/system/org.gnome.SettingsDaemon.Sharing.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/panels/system/remote-desktop.css b/panels/system/remote-desktop.css new file mode 100644 index 0000000000000000000000000000000000000000..cf18ab392d2209616989e104d9ad29ae9a4db636 --- /dev/null +++ b/panels/system/remote-desktop.css @@ -0,0 +1,4 @@ +.tls-cert-fingerprint { + font-family: monospace; + margin: 10px; +} diff --git a/panels/system/system.gresource.xml b/panels/system/system.gresource.xml index 125d72dc34c03f52b72da0b149d093fc7f9f571a..b236ce2895ac71fd15a2cd0551973a84d2d189c0 100644 --- a/panels/system/system.gresource.xml +++ b/panels/system/system.gresource.xml @@ -2,5 +2,7 @@ cc-system-panel.ui + cc-system-remote-desktop-page.ui + remote-desktop.css