/* * Copyright (C) 2024 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. * * Written by: * Joan Torres */ #include "config.h" #include #include #include #include #include #ifdef HAVE_RDP #include #endif #include "grd-dbus-remote-desktop.h" #include "grd-private.h" #include "grd-settings-system.h" #include "grd-utils.h" #define GRD_CONFIGURATION_TIMEOUT_S 10 #define GRD_SYSTEMD_SERVICE "gnome-remote-desktop.service" #define GRD_SERVER_USER_CERT_SUBDIR "certificates" #define GRD_CONFIGURE_SYSTEM_DAEMON_POLKIT_ACTION "org.gnome.remotedesktop.configure-system-daemon" #define GRD_MAX_CERTIFICATE_FILE_SIZE_BYTES (50 * 1024) #define GRD_MAX_PRIVATE_KEY_FILE_SIZE_BYTES (50 * 1024) typedef struct { unsigned int *timeout_source_id; GSourceFunc function; gpointer data; } TimeoutLocker; struct _GrdConfiguration { GApplication parent; PolkitAuthority *authority; GrdSettingsSystem *settings; GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server; unsigned int own_name_source_id; GDBusProxy *unit_proxy; GrdSystemdUnitActiveState unit_state; unsigned int timeout_source_id; unsigned int sigint_source_id; unsigned int sigterm_source_id; }; #define GRD_TYPE_CONFIGURATION (grd_configuration_get_type ()) G_DECLARE_FINAL_TYPE (GrdConfiguration, grd_configuration, GRD, CONFIGURATION, GApplication) G_DEFINE_TYPE (GrdConfiguration, grd_configuration, G_TYPE_APPLICATION) static void timeout_locker_free (TimeoutLocker *locker); G_DEFINE_AUTOPTR_CLEANUP_FUNC (TimeoutLocker, timeout_locker_free) #ifdef HAVE_RDP G_DEFINE_AUTOPTR_CLEANUP_FUNC (rdpCertificate, freerdp_certificate_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC (rdpPrivateKey, freerdp_key_free) #endif static TimeoutLocker * timeout_locker_new (unsigned int *timeout_source_id, GSourceFunc function, gpointer data) { TimeoutLocker *locker = g_new0 (TimeoutLocker, 1); locker->timeout_source_id = timeout_source_id; locker->function = function; locker->data = data; g_clear_handle_id (locker->timeout_source_id, g_source_remove); return locker; } static void timeout_locker_free (TimeoutLocker *locker) { if (*locker->timeout_source_id == 0) { *locker->timeout_source_id = g_timeout_add_seconds (GRD_CONFIGURATION_TIMEOUT_S, locker->function, locker->data); } g_free (locker); } static void grd_configuration_init (GrdConfiguration *app) { } static void on_unit_active_state_changed (GrdConfiguration *configuration) { if (!grd_systemd_unit_get_active_state (configuration->unit_proxy, &configuration->unit_state, NULL)) return; g_object_notify (G_OBJECT (configuration->settings), "rdp-enabled"); } static void watch_grd_system_unit_active_state (GrdConfiguration *configuration) { GDBusProxy *unit_proxy = NULL; g_autoptr (GError) error = NULL; if (!grd_systemd_get_unit (G_BUS_TYPE_SYSTEM, GRD_SYSTEMD_SERVICE, &unit_proxy, &error)) { g_warning ("Could not load %s: %s", GRD_SYSTEMD_SERVICE, error->message); return; } grd_systemd_unit_get_active_state (unit_proxy, &configuration->unit_state, NULL); g_signal_connect_object (G_OBJECT (unit_proxy), "g-properties-changed", G_CALLBACK (on_unit_active_state_changed), configuration, G_CONNECT_SWAPPED); configuration->unit_proxy = unit_proxy; } static gboolean transform_enabled (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { GrdConfiguration *configuration = user_data; gboolean enabled; GrdSystemdUnitActiveState active_state; enabled = g_value_get_boolean (from_value); active_state = configuration->unit_state; g_value_set_boolean (to_value, enabled && (active_state == GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVE || active_state == GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVATING)); return TRUE; } static gboolean on_handle_enable (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GrdConfiguration *configuration) { g_autoptr (GError) error = NULL; if (!grd_toggle_systemd_unit (GRD_RUNTIME_MODE_SYSTEM, TRUE, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed enabling %s: %s", GRD_SYSTEMD_SERVICE, error->message); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_object_set (G_OBJECT (configuration->settings), "rdp-enabled", TRUE, NULL); grd_dbus_remote_desktop_configuration_rdp_server_complete_enable ( configuration_rdp_server, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean on_handle_disable (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GrdConfiguration *configuration) { g_autoptr (GError) error = NULL; if (!grd_toggle_systemd_unit (GRD_RUNTIME_MODE_SYSTEM, FALSE, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed disabling %s: %s", GRD_SYSTEMD_SERVICE, error->message); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_object_set (G_OBJECT (configuration->settings), "rdp-enabled", FALSE, NULL); grd_dbus_remote_desktop_configuration_rdp_server_complete_disable ( configuration_rdp_server, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean on_handle_get_credentials (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GrdConfiguration *configuration) { g_autofree char *username = NULL; g_autofree char *password = NULL; g_autoptr (GError) error = NULL; GVariantBuilder credentials; g_variant_builder_init (&credentials, G_VARIANT_TYPE ("a{sv}")); grd_settings_get_rdp_credentials (GRD_SETTINGS (configuration->settings), &username, &password, &error); if (error) g_warning ("[Configuration] Failed to get credentials: %s", error->message); if (!username) username = g_strdup (""); if (!password) password = g_strdup (""); g_variant_builder_add (&credentials, "{sv}", "username", g_variant_new_string (username)); g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password)); grd_dbus_remote_desktop_configuration_rdp_server_complete_get_credentials ( configuration_rdp_server, invocation, g_variant_builder_end (&credentials)); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean on_handle_set_credentials (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GVariant *credentials, GrdConfiguration *configuration) { g_autofree char *old_username = NULL; g_autofree char *old_password = NULL; g_autofree char *username = NULL; g_autofree char *password = NULL; g_autoptr (GError) error = NULL; g_variant_lookup (credentials, "username", "s", &username); g_variant_lookup (credentials, "password", "s", &password); if (!username && !password) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Username or password expected " "in credentials"); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (!username || !password) { grd_settings_get_rdp_credentials (GRD_SETTINGS (configuration->settings), &old_username, &old_password, NULL); } if (!username) username = g_steal_pointer (&old_username); if (!password) password = g_steal_pointer (&old_password); if (!grd_settings_set_rdp_credentials (GRD_SETTINGS (configuration->settings), username, password, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to set credentials: %s", error->message); return G_DBUS_METHOD_INVOCATION_HANDLED; } grd_dbus_remote_desktop_configuration_rdp_server_complete_set_credentials ( configuration_rdp_server, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean on_handle_import_certificate (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GUnixFDList *fd_list, GVariant *certificate, GVariant *private_key, GrdConfiguration *configuration) { g_autoptr (rdpCertificate) rdp_certificate = NULL; g_autoptr (rdpPrivateKey) rdp_private_key = NULL; g_autofree char *certificate_filename = NULL; g_autofree char *key_filename = NULL; g_autoptr (GError) error = NULL; g_autofd int certificate_fd = -1; g_autofd int key_fd = -1; GFileTest fd_test_results; int certificate_fd_index = -1; int key_fd_index = -1; gboolean success; g_variant_get (certificate, "(sh)", &certificate_filename, &certificate_fd_index); g_variant_get (private_key, "(sh)", &key_filename, &key_fd_index); if (!G_IS_UNIX_FD_LIST (fd_list)) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire " "file descriptor for certificate and private key: " "The sender supplied an invalid fd list"); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (certificate_fd_index < 0 || certificate_fd_index >= g_unix_fd_list_get_length (fd_list)) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire " "file descriptor for certificate: " "The sender supplied an invalid fd index"); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (key_fd_index < 0 || key_fd_index >= g_unix_fd_list_get_length (fd_list)) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to acquire " "file descriptor for private key: " "The sender supplied an invalid fd index"); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } certificate_fd = g_unix_fd_list_get (fd_list, certificate_fd_index, &error); if (certificate_fd == -1) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } success = grd_test_fd (certificate_fd, GRD_MAX_CERTIFICATE_FILE_SIZE_BYTES, &fd_test_results, &error); if (!success) { g_prefix_error (&error, "Could not inspect certificate file descriptor"); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (!(fd_test_results & G_FILE_TEST_IS_REGULAR)) { g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, "Invalid certificate file"); return G_DBUS_METHOD_INVOCATION_HANDLED; } key_fd = g_unix_fd_list_get (fd_list, key_fd_index, &error); if (key_fd == -1) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } success = grd_test_fd (key_fd, GRD_MAX_PRIVATE_KEY_FILE_SIZE_BYTES, &fd_test_results, &error); if (!success) { g_prefix_error (&error, "Could not inspect private key file descriptor"); g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (!(fd_test_results & G_FILE_TEST_IS_REGULAR)) { g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, "Invalid private key file"); return G_DBUS_METHOD_INVOCATION_HANDLED; } grd_rewrite_path_to_user_data_dir (&certificate_filename, GRD_SERVER_USER_CERT_SUBDIR, "rdp-tls.crt"); if (!grd_write_fd_to_file (certificate_fd, certificate_filename, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (certificate_filename) rdp_certificate = freerdp_certificate_new_from_file (certificate_filename); if (!rdp_certificate) { g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid certificate"); return G_DBUS_METHOD_INVOCATION_HANDLED; } grd_rewrite_path_to_user_data_dir (&key_filename, GRD_SERVER_USER_CERT_SUBDIR, "rdp-tls.key"); if (!grd_write_fd_to_file (key_fd, key_filename, NULL, &error)) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } if (key_filename) rdp_private_key = freerdp_key_new_from_file (key_filename); if (!rdp_private_key) { g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid private key"); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_object_set (configuration->settings, "rdp-server-cert-path", certificate_filename, "rdp-server-key-path", key_filename, NULL); grd_dbus_remote_desktop_configuration_rdp_server_complete_import_certificate ( configuration_rdp_server, invocation, fd_list); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean terminate (gpointer user_data) { GrdConfiguration *configuration = user_data; g_clear_handle_id (&configuration->timeout_source_id, g_source_remove); g_clear_handle_id (&configuration->sigint_source_id, g_source_remove); g_clear_handle_id (&configuration->sigterm_source_id, g_source_remove); g_application_release (G_APPLICATION (configuration)); return G_SOURCE_REMOVE; } static gboolean ensure_polkit_authority (GrdConfiguration *configuration, GError **error) { if (configuration->authority) return TRUE; configuration->authority = polkit_authority_get_sync (NULL, error); return configuration->authority != NULL; } static gboolean on_authorize_method (GrdDBusRemoteDesktopConfigurationRdpServer *configuration_rdp_server, GDBusMethodInvocation *invocation, GrdConfiguration *configuration) { g_autoptr (PolkitAuthorizationResult) result = NULL; g_autoptr (PolkitSubject) subject = NULL; g_autoptr (TimeoutLocker) locker = NULL; PolkitCheckAuthorizationFlags flags; g_autoptr (GError) error = NULL; const char *sender = NULL; const char *action = NULL; locker = timeout_locker_new (&configuration->timeout_source_id, terminate, configuration); if (!ensure_polkit_authority (configuration, &error)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Couldn't get polkit authority: %s", error->message); return FALSE; } sender = g_dbus_method_invocation_get_sender (invocation); subject = polkit_system_bus_name_new (sender); action = GRD_CONFIGURE_SYSTEM_DAEMON_POLKIT_ACTION; flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; result = polkit_authority_check_authorization_sync (configuration->authority, subject, action, NULL, flags, NULL, &error); if (!result) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to check authorization: %s", error->message); return FALSE; } if (!polkit_authorization_result_get_is_authorized (result)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Not authorized for action %s", action); return FALSE; } return TRUE; } static void on_bus_acquired (GDBusConnection *connection, const char *name, gpointer user_data) { GrdConfiguration *configuration = user_data; g_debug ("[Configuration] Now on system bus"); g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (configuration->configuration_rdp_server), connection, REMOTE_DESKTOP_CONFIGURATION_OBJECT_PATH, NULL); } static void on_name_acquired (GDBusConnection *connection, const char *name, gpointer user_data) { g_debug ("[Configuration] Owned %s name", name); } static void on_name_lost (GDBusConnection *connection, const char *name, gpointer user_data) { g_debug ("[Configuration] Lost owned %s name", name); } static void register_signals (GrdConfiguration *configuration) { configuration->timeout_source_id = g_timeout_add_seconds (GRD_CONFIGURATION_TIMEOUT_S, terminate, configuration); configuration->sigint_source_id = g_unix_signal_add (SIGINT, terminate, configuration); configuration->sigterm_source_id = g_unix_signal_add (SIGTERM, terminate, configuration); } static void grd_configuration_startup (GApplication *application) { GrdConfiguration *configuration = GRD_CONFIGURATION (application); configuration->settings = grd_settings_system_new (); grd_settings_system_use_local_state (configuration->settings); configuration->configuration_rdp_server = grd_dbus_remote_desktop_configuration_rdp_server_skeleton_new (); watch_grd_system_unit_active_state (configuration); g_object_bind_property_full (configuration->settings, "rdp-enabled", configuration->configuration_rdp_server, "enabled", G_BINDING_SYNC_CREATE, transform_enabled, NULL, configuration, NULL); g_object_bind_property (configuration->settings, "rdp-port", configuration->configuration_rdp_server, "port", G_BINDING_SYNC_CREATE); g_object_bind_property (configuration->settings, "rdp-server-cert-path", configuration->configuration_rdp_server, "tls-cert", G_BINDING_SYNC_CREATE); g_object_bind_property (configuration->settings, "rdp-server-fingerprint", configuration->configuration_rdp_server, "tls-fingerprint", G_BINDING_SYNC_CREATE); g_object_bind_property (configuration->settings, "rdp-server-key-path", configuration->configuration_rdp_server, "tls-key", G_BINDING_SYNC_CREATE); g_object_bind_property (configuration->settings, "rdp-auth-methods", configuration->configuration_rdp_server, "auth-methods", G_BINDING_SYNC_CREATE); g_signal_connect_object (configuration->configuration_rdp_server, "handle-enable", G_CALLBACK (on_handle_enable), configuration, 0); g_signal_connect_object (configuration->configuration_rdp_server, "handle-disable", G_CALLBACK (on_handle_disable), configuration, 0); g_signal_connect_object (configuration->configuration_rdp_server, "handle-get-credentials", G_CALLBACK (on_handle_get_credentials), configuration, 0); g_signal_connect_object (configuration->configuration_rdp_server, "handle-set-credentials", G_CALLBACK (on_handle_set_credentials), configuration, 0); g_signal_connect_object (configuration->configuration_rdp_server, "handle-import-certificate", G_CALLBACK (on_handle_import_certificate), configuration, 0); g_signal_connect_object (configuration->configuration_rdp_server, "g-authorize-method", G_CALLBACK (on_authorize_method), configuration, 0); configuration->own_name_source_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, REMOTE_DESKTOP_CONFIGURATION_BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, on_name_acquired, on_name_lost, configuration, NULL); register_signals (configuration); g_application_hold (application); G_APPLICATION_CLASS (grd_configuration_parent_class)->startup (application); } static void grd_configuration_shutdown (GApplication *application) { GrdConfiguration *configuration = GRD_CONFIGURATION (application); g_clear_object (&configuration->authority); g_clear_object (&configuration->settings); g_clear_object (&configuration->unit_proxy); g_dbus_interface_skeleton_unexport ( G_DBUS_INTERFACE_SKELETON (configuration->configuration_rdp_server)); g_clear_object (&configuration->configuration_rdp_server); g_clear_handle_id (&configuration->own_name_source_id, g_bus_unown_name); g_clear_handle_id (&configuration->timeout_source_id, g_source_remove); g_clear_handle_id (&configuration->sigint_source_id, g_source_remove); g_clear_handle_id (&configuration->sigterm_source_id, g_source_remove); G_APPLICATION_CLASS (grd_configuration_parent_class)->shutdown (application); } static void grd_configuration_class_init (GrdConfigurationClass *klass) { GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass); g_application_class->startup = grd_configuration_startup; g_application_class->shutdown = grd_configuration_shutdown; } int main (int argc, char **argv) { g_autoptr (GApplication) app = NULL; app = g_object_new (grd_configuration_get_type (), "application-id", REMOTE_DESKTOP_CONFIGURATION_BUS_NAME, "flags", G_APPLICATION_IS_SERVICE, NULL); return g_application_run (app, argc, argv); }