/*
 * 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 <joan.torres@suse.com>
 */

#include "config.h"

#include <gio/gio.h>
#include <glib-unix.h>
#include <glib/gstdio.h>
#include <glib.h>
#include <polkit/polkit.h>

#ifdef HAVE_RDP
#include <freerdp/freerdp.h>
#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 (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 (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;
    }

  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;
    }

  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_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);
}
