/* * Copyright (C) 2023 SUSE Software Solutions Germany GmbH * * 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-settings-system.h" #include #include #define GRD_SETTINGS_SYSTEM_GROUP_RDP "RDP" typedef enum { GRD_SETTINGS_SOURCE_TYPE_INVALID = -1, GRD_SETTINGS_SOURCE_TYPE_DEFAULT = 0, GRD_SETTINGS_SOURCE_TYPE_CUSTOM = 1, GRD_SETTINGS_SOURCE_TYPE_LOCAL_STATE = 2, N_GRD_SETTINGS_SOURCES } GrdSettingsSourceType; typedef struct { GKeyFile *key_file; GFileMonitor *file_monitor; GrdSettingsSourceType type; } GrdSettingsSource; typedef struct { const char *file_key; const char *settings_name; void (* read_settings_value) (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name); void (* write_settings_value) (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name); } FileSetting; struct _GrdSettingsSystem { GrdSettings parent; GrdSettingsSource *setting_sources[N_GRD_SETTINGS_SOURCES]; GKeyFile *key_file; gboolean use_local_state; }; G_DEFINE_TYPE (GrdSettingsSystem, grd_settings_system, GRD_TYPE_SETTINGS) static void grd_settings_system_reload_sources (GrdSettingsSystem *settings_system); static void read_rdp_file_settings (GrdSettingsSystem *settings_system); static GrdSettingsSource * grd_settings_source_new (GrdSettingsSourceType source_type, const char *file_path) { g_autofree GrdSettingsSource *source = NULL; GKeyFileFlags flags = G_KEY_FILE_NONE; g_autoptr (GKeyFile) key_file = NULL; g_autoptr (GError) error = NULL; g_autoptr (GFile) file = NULL; source = g_new0 (GrdSettingsSource, 1); key_file = g_key_file_new (); if (source_type == GRD_SETTINGS_SOURCE_TYPE_CUSTOM) flags |= G_KEY_FILE_KEEP_COMMENTS; if (!g_key_file_load_from_file (key_file, file_path, flags, &error)) { g_debug ("Failed to load key file from '%s': %s", file_path, error->message); return NULL; } source->key_file = g_steal_pointer (&key_file); file = g_file_new_for_path (file_path); source->file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); if (!source->file_monitor) { g_warning ("Failed to monitor file '%s': %s", file_path, error->message); return NULL; } return g_steal_pointer (&source); } static void grd_settings_source_free (GrdSettingsSource *source) { g_clear_pointer (&source->key_file, g_key_file_unref); g_clear_object (&source->file_monitor); g_clear_pointer (&source, g_free); } static char * get_local_state_conf (void) { return g_build_filename (g_get_user_data_dir (), "gnome-remote-desktop", "grd.conf", NULL); } void grd_settings_system_use_local_state (GrdSettingsSystem *settings_system) { g_assert (GRD_IS_SETTINGS_SYSTEM (settings_system)); settings_system->use_local_state = TRUE; } static void merge_descendant_keys (GrdSettingsSystem *settings_system, GKeyFile *key_file, GKeyFile *descendant_key_file) { g_auto (GStrv) groups = NULL; size_t group_count = 0; size_t i, j; g_assert (key_file); g_assert (descendant_key_file); groups = g_key_file_get_groups (descendant_key_file, &group_count); for (i = 0; i < group_count; i++) { g_auto (GStrv) keys = NULL; size_t key_count = 0; keys = g_key_file_get_keys (descendant_key_file, groups[i], &key_count, NULL); for (j = 0; j < key_count; j++) { g_autofree char *value = NULL; value = g_key_file_get_value (descendant_key_file, groups[i], keys[j], NULL); if (value) g_key_file_set_value (key_file, groups[i], keys[j], value); } } } static gboolean prune_inherited_keys (GrdSettingsSystem *settings_system, GrdSettingsSourceType source_type, GKeyFile *key_file_to_prune) { g_auto (GStrv) groups = NULL; size_t group_count = 0; size_t i, j; if (source_type == GRD_SETTINGS_SOURCE_TYPE_DEFAULT) return TRUE; if (source_type != GRD_SETTINGS_SOURCE_TYPE_CUSTOM) g_key_file_remove_comment (key_file_to_prune, NULL, NULL, NULL); groups = g_key_file_get_groups (key_file_to_prune, &group_count); for (i = 0; i < group_count; i++) { g_auto (GStrv) keys = NULL; size_t key_count; if (source_type != GRD_SETTINGS_SOURCE_TYPE_CUSTOM) g_key_file_remove_comment (key_file_to_prune, groups[i], NULL, NULL); keys = g_key_file_get_keys (key_file_to_prune, groups[i], &key_count, NULL); for (j = 0; j < key_count; j++) { g_autofree char *value_to_prune = NULL; GrdSettingsSourceType ancestor_type; gboolean should_prune_key = FALSE; value_to_prune = g_key_file_get_value (key_file_to_prune, groups[i], keys[j], NULL); if (source_type != GRD_SETTINGS_SOURCE_TYPE_CUSTOM) { g_key_file_remove_comment (key_file_to_prune, groups[i], keys[j], NULL); } for (ancestor_type = source_type - 1; ancestor_type >= GRD_SETTINGS_SOURCE_TYPE_DEFAULT; ancestor_type--) { GrdSettingsSource *ancestor_source; ancestor_source = settings_system->setting_sources[ancestor_type]; if (ancestor_source == NULL) continue; if (g_key_file_has_key (ancestor_source->key_file, groups[i], keys[j], NULL)) { g_autofree char *ancestor_value = NULL; ancestor_value = g_key_file_get_value (ancestor_source->key_file, groups[i], keys[j], NULL); if (g_strcmp0 (value_to_prune, ancestor_value) == 0) should_prune_key = TRUE; break; } } if (should_prune_key) { g_key_file_remove_key (key_file_to_prune, groups[i], keys[j], NULL); } } g_clear_pointer (&keys, g_strfreev); keys = g_key_file_get_keys (key_file_to_prune, groups[i], &key_count, NULL); if (key_count == 0) g_key_file_remove_group (key_file_to_prune, groups[i], NULL); } g_clear_pointer (&groups, g_strfreev); groups = g_key_file_get_groups (key_file_to_prune, &group_count); if (group_count == 0) return FALSE; return TRUE; } static char ** get_conf_paths (void) { g_autofree char *local_state_conf = NULL; char **paths = NULL; local_state_conf = get_local_state_conf (); paths = g_new (char *, N_GRD_SETTINGS_SOURCES + 1); paths[GRD_SETTINGS_SOURCE_TYPE_DEFAULT] = g_strdup (GRD_DEFAULT_CONF); paths[GRD_SETTINGS_SOURCE_TYPE_CUSTOM] = g_strdup (GRD_CUSTOM_CONF); paths[GRD_SETTINGS_SOURCE_TYPE_LOCAL_STATE] = g_steal_pointer (&local_state_conf); paths[N_GRD_SETTINGS_SOURCES] = NULL; return paths; } static void on_file_changed (GFileMonitor *file_monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { GrdSettingsSystem *settings_system = GRD_SETTINGS_SYSTEM (user_data); if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { grd_settings_system_reload_sources (settings_system); read_rdp_file_settings (settings_system); } } static void grd_settings_system_reload_sources (GrdSettingsSystem *settings_system) { g_autoptr (GKeyFile) key_file = NULL; g_autoptr (GError) error = NULL; g_auto (GStrv) paths = NULL; size_t source_type; paths = get_conf_paths (); key_file = g_key_file_new (); for (source_type = GRD_SETTINGS_SOURCE_TYPE_DEFAULT; source_type < N_GRD_SETTINGS_SOURCES; source_type++) { GrdSettingsSource *source = NULL; g_clear_pointer (&settings_system->setting_sources[source_type], grd_settings_source_free); source = grd_settings_source_new (source_type, paths[source_type]); if (!source) continue; merge_descendant_keys (settings_system, key_file, source->key_file); g_signal_connect (source->file_monitor, "changed", G_CALLBACK (on_file_changed), settings_system); settings_system->setting_sources[source_type] = g_steal_pointer (&source); } g_clear_pointer (&settings_system->key_file, g_key_file_unref); settings_system->key_file = g_steal_pointer (&key_file); } GrdSettingsSystem * grd_settings_system_new (void) { return g_object_new (GRD_TYPE_SETTINGS_SYSTEM, "runtime-mode", GRD_RUNTIME_MODE_SYSTEM, "rdp-view-only", FALSE, "rdp-screen-share-mode", GRD_RDP_SCREEN_SHARE_MODE_EXTEND, NULL); } static void write_string (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { g_autofree char *value = NULL; g_object_get (G_OBJECT (settings_system), settings_name, &value, NULL); g_key_file_set_string (key_file, group, key, value); } static void read_filename (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { g_autofree char *value = NULL; g_autoptr (GError) error = NULL; value = g_key_file_get_string (key_file, group, key, &error); if (error && error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) return; if (!value) return; if (!g_file_test (value, G_FILE_TEST_IS_REGULAR)) return; g_object_set (G_OBJECT (settings_system), settings_name, value, NULL); } static void read_int (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { g_autoptr (GError) error = NULL; int value; value = g_key_file_get_integer (key_file, group, key, &error); if (error) return; g_object_set (G_OBJECT (settings_system), settings_name, value, NULL); } static void write_int (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { int value = 0; g_object_get (G_OBJECT (settings_system), settings_name, &value, NULL); g_key_file_set_integer (key_file, group, key, value); } static void read_boolean (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { g_autoptr (GError) error = NULL; gboolean value = FALSE; value = g_key_file_get_boolean (key_file, group, key, &error); if (error) return; g_object_set (G_OBJECT (settings_system), settings_name, value, NULL); } static void write_boolean (GrdSettingsSystem *settings_system, GKeyFile *key_file, const char *group, const char *key, const char *settings_name) { gboolean value = FALSE; g_object_get (G_OBJECT (settings_system), settings_name, &value, NULL); g_key_file_set_boolean (key_file, group, key, value); } static const FileSetting rdp_file_settings[] = { { "enabled", "rdp-enabled", read_boolean, write_boolean }, { "tls-cert", "rdp-server-cert-path", read_filename, write_string }, { "tls-key", "rdp-server-key-path", read_filename, write_string }, { "port", "rdp-port", read_int, write_int }, }; static void on_rdp_setting_changed (GrdSettingsSystem *settings_system, GParamSpec *pspec, FileSetting *file_setting) { GrdSettingsSourceType settings_source_type = GRD_SETTINGS_SOURCE_TYPE_INVALID; g_autofree char *local_state_conf = NULL; g_autoptr (GKeyFile) key_file = NULL; g_autoptr (GError) error = NULL; g_autofree char *data = NULL; const char *filename; size_t length = 0; grd_settings_system_reload_sources (settings_system); file_setting->write_settings_value (settings_system, settings_system->key_file, GRD_SETTINGS_SYSTEM_GROUP_RDP, file_setting->file_key, file_setting->settings_name); data = g_key_file_to_data (settings_system->key_file, &length, NULL); key_file = g_key_file_new (); if (!g_key_file_load_from_data (key_file, data, length, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error)) { g_warning ("Failed to copy loaded key file: %s", error->message); return; } if (settings_system->use_local_state) settings_source_type = GRD_SETTINGS_SOURCE_TYPE_LOCAL_STATE; else settings_source_type = GRD_SETTINGS_SOURCE_TYPE_CUSTOM; local_state_conf = get_local_state_conf (); switch (settings_source_type) { case GRD_SETTINGS_SOURCE_TYPE_LOCAL_STATE: filename = local_state_conf; break; case GRD_SETTINGS_SOURCE_TYPE_CUSTOM: g_remove (local_state_conf); filename = GRD_CUSTOM_CONF; break; default: g_assert_not_reached (); } if (prune_inherited_keys (settings_system, settings_source_type, key_file)) { if (!g_key_file_save_to_file (key_file, filename, &error)) { g_warning ("Failed to write %s: %s", file_setting->file_key, error->message); } } else { g_remove (filename); } } static void read_rdp_file_settings (GrdSettingsSystem *settings_system) { g_autoptr (GKeyFile) key_file = NULL; g_autoptr (GError) error = NULL; int i; for (i = 0; i < G_N_ELEMENTS (rdp_file_settings); i++) { g_signal_handlers_block_by_func (G_OBJECT (settings_system), G_CALLBACK (on_rdp_setting_changed), (gpointer) &rdp_file_settings[i]); rdp_file_settings[i].read_settings_value ( settings_system, settings_system->key_file, GRD_SETTINGS_SYSTEM_GROUP_RDP, rdp_file_settings[i].file_key, rdp_file_settings[i].settings_name); g_signal_handlers_unblock_by_func (G_OBJECT (settings_system), G_CALLBACK (on_rdp_setting_changed), (gpointer) &rdp_file_settings[i]); } } static void register_write_rdp_file_settings (GrdSettingsSystem *settings_system) { int i; for (i = 0; i < G_N_ELEMENTS (rdp_file_settings); i++) { g_autofree char *signal_name = NULL; signal_name = g_strdup_printf ("notify::%s", rdp_file_settings[i].settings_name); g_signal_connect (G_OBJECT (settings_system), signal_name, G_CALLBACK (on_rdp_setting_changed), (gpointer) &rdp_file_settings[i]); } } static void grd_settings_system_constructed (GObject *object) { GrdSettingsSystem *settings_system = GRD_SETTINGS_SYSTEM (object); grd_settings_system_reload_sources (settings_system); read_rdp_file_settings (settings_system); register_write_rdp_file_settings (settings_system); G_OBJECT_CLASS (grd_settings_system_parent_class)->constructed (object); } static void grd_settings_system_finalize (GObject *object) { GrdSettingsSystem *settings_system = GRD_SETTINGS_SYSTEM (object); size_t i; g_clear_pointer (&settings_system->key_file, g_key_file_unref); for (i = 0; i < N_GRD_SETTINGS_SOURCES; i++) { g_clear_pointer (&settings_system->setting_sources[i], grd_settings_source_free); } G_OBJECT_CLASS (grd_settings_system_parent_class)->finalize (object); } static void grd_settings_system_init (GrdSettingsSystem *settings_system) { } static void grd_settings_system_class_init (GrdSettingsSystemClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = grd_settings_system_constructed; object_class->finalize = grd_settings_system_finalize; }