Blame SOURCES/0001-subman-Add-a-new-plugin-to-provide-system-subscripti.patch

e296c6
From bee6d42503ec9b56f6e10704db02bcedb6a4fdf1 Mon Sep 17 00:00:00 2001
e296c6
From: Richard Hughes <rhughes@redhat.com>
e296c6
Date: Thu, 20 Aug 2020 11:16:09 -0400
e296c6
Subject: [PATCH 01/15] subman: Add a new plugin to provide system subscription
e296c6
 registration
e296c6
e296c6
---
e296c6
 meson.build                                   |   1 +
e296c6
 plugins/meson.build                           |   1 +
e296c6
 plugins/subman/README.md                      |  56 +
e296c6
 plugins/subman/gsd-subman-common.c            |  36 +
e296c6
 plugins/subman/gsd-subman-common.h            |  40 +
e296c6
 plugins/subman/gsd-subman-helper.c            | 378 +++++++
e296c6
 plugins/subman/gsd-subscription-manager.c     | 982 ++++++++++++++++++
e296c6
 plugins/subman/gsd-subscription-manager.h     |  63 ++
e296c6
 plugins/subman/main.c                         |   8 +
e296c6
 plugins/subman/meson.build                    |  56 +
e296c6
 ...ome.SettingsDaemon.Subscription.desktop.in |   9 +
e296c6
 ...ettings-daemon.plugins.subman.policy.in.in |  27 +
e296c6
 ...gnome.settings-daemon.plugins.subman.rules |   7 +
e296c6
 13 files changed, 1664 insertions(+)
e296c6
 create mode 100644 plugins/subman/README.md
e296c6
 create mode 100644 plugins/subman/gsd-subman-common.c
e296c6
 create mode 100644 plugins/subman/gsd-subman-common.h
e296c6
 create mode 100644 plugins/subman/gsd-subman-helper.c
e296c6
 create mode 100644 plugins/subman/gsd-subscription-manager.c
e296c6
 create mode 100644 plugins/subman/gsd-subscription-manager.h
e296c6
 create mode 100644 plugins/subman/main.c
e296c6
 create mode 100644 plugins/subman/meson.build
e296c6
 create mode 100644 plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
e296c6
 create mode 100644 plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
e296c6
 create mode 100644 plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
e296c6
e296c6
diff --git a/meson.build b/meson.build
e296c6
index 1632ea05..27bf8c4c 100644
e296c6
--- a/meson.build
e296c6
+++ b/meson.build
e296c6
@@ -70,60 +70,61 @@ if gsd_buildtype.contains('debug')
e296c6
   ]
e296c6
 
e296c6
   compiler_flags = cc.get_supported_arguments(test_cflags)
e296c6
 elif gsd_buildtype.contains('release')
e296c6
   common_flags += ['-DG_DISABLE_CAST_CHECKS']
e296c6
 endif
e296c6
 
e296c6
 # Workaround for meson's bug
e296c6
 # https://github.com/mesonbuild/meson/pull/1896
e296c6
 if get_option('b_ndebug') == true
e296c6
   common_flags += ['-DG_DISABLE_ASSERT']
e296c6
 endif
e296c6
 
e296c6
 add_project_arguments(common_flags + compiler_flags, language: 'c')
e296c6
 
e296c6
 glib_dep = dependency('glib-2.0', version: '>= 2.56')
e296c6
 colord_dep = dependency('colord', version: '>= 1.0.2')
e296c6
 geocode_glib_dep = dependency('geocode-glib-1.0', version: '>= 3.10.0')
e296c6
 gio_dep = dependency('gio-2.0', version: '>= 2.53.0')
e296c6
 gio_unix_dep = dependency('gio-unix-2.0')
e296c6
 gnome_desktop_dep = dependency('gnome-desktop-3.0', version: '>= 3.11.1')
e296c6
 gsettings_desktop_dep = dependency('gsettings-desktop-schemas', version: '>= 3.27.90')
e296c6
 gtk_dep = dependency('gtk+-3.0', version: '>= 3.15.3')
e296c6
 gtk_x11_dep = dependency('gtk+-x11-3.0')
e296c6
 gweather_dep = dependency('gweather-3.0', version: '>= 3.9.5')
e296c6
 lcms_dep = dependency('lcms2', version: '>= 2.2')
e296c6
 libcanberra_gtk_dep = dependency('libcanberra-gtk3')
28c014
 libgeoclue_dep = dependency('libgeoclue-2.0', version: '>= 2.3.1')
28c014
 libnotify_dep = dependency('libnotify', version: '>= 0.7.3')
28c014
 libpulse_mainloop_glib_dep = dependency('libpulse-mainloop-glib', version: '>= 2.0')
28c014
+jsonglib_dep = dependency('json-glib-1.0', version: '>= 1.1.1')
28c014
 pango_dep = dependency('pango', version: '>= 1.20.0')
28c014
 polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.103')
28c014
 upower_glib_dep = dependency('upower-glib', version: '>= 0.99.0')
e296c6
 x11_dep = dependency('x11')
e296c6
 
e296c6
 m_dep = cc.find_library('m')
e296c6
 dl_dep = cc.find_library('dl')
e296c6
 
e296c6
 # ALSA integration (default enabled)
e296c6
 enable_alsa = get_option('alsa')
e296c6
 assert(enable_alsa or not host_is_linux, 'ALSA is not optional on Linux platforms')
e296c6
 
e296c6
 libgvc = subproject(
e296c6
   'gvc',
e296c6
   default_options: [
e296c6
     'static=true',
e296c6
     'alsa=' + enable_alsa.to_string()
e296c6
   ]
e296c6
 )
e296c6
 libgvc_dep = libgvc.get_variable('libgvc_dep')
e296c6
 
e296c6
 # GUdev integration (default enabled)
e296c6
 enable_gudev = get_option('gudev')
e296c6
 if enable_gudev
e296c6
   gudev_dep = dependency('gudev-1.0')
e296c6
 endif
e296c6
 config_h.set10('HAVE_GUDEV', enable_gudev)
e296c6
 if host_is_linux
e296c6
   assert(enable_gudev, 'GUdev is not optional on Linux platforms')
e296c6
 endif
e296c6
diff --git a/plugins/meson.build b/plugins/meson.build
e296c6
index 3c4d42ac..4c9caf47 100644
e296c6
--- a/plugins/meson.build
e296c6
+++ b/plugins/meson.build
e296c6
@@ -1,35 +1,36 @@
e296c6
 enabled_plugins = [
e296c6
   ['a11y-settings', 'A11ySettings'],
28c014
   ['account', 'Account'],
28c014
   ['clipboard', 'Clipboard'],
28c014
   ['color', 'Color'],
28c014
+  ['subman', 'Subscription'],
28c014
   ['datetime', 'Datetime'],
28c014
   ['dummy', ''],
28c014
   ['power', 'Power'],
e296c6
   ['housekeeping', 'Housekeeping'],
e296c6
   ['keyboard', 'Keyboard'],
e296c6
   ['media-keys', 'MediaKeys'],
e296c6
   ['mouse', 'Mouse'],
e296c6
   ['screensaver-proxy', 'ScreensaverProxy'],
e296c6
   ['sharing', 'Sharing'],
e296c6
   ['sound', 'Sound'],
e296c6
   ['xsettings', 'XSettings']
e296c6
 ]
e296c6
 
e296c6
 if enable_smartcard
e296c6
   enabled_plugins += [['smartcard', 'Smartcard']]
e296c6
 endif
e296c6
 
e296c6
 if enable_wacom
e296c6
   enabled_plugins += [['wacom', 'Wacom']]
e296c6
 endif
e296c6
 
e296c6
 if enable_cups
e296c6
   enabled_plugins += [['print-notifications', 'PrintNotifications']]
e296c6
 endif
e296c6
 
e296c6
 if enable_rfkill
e296c6
   enabled_plugins += [['rfkill', 'Rfkill']]
e296c6
 endif
e296c6
 
e296c6
 plugins_conf = configuration_data()
e296c6
diff --git a/plugins/subman/README.md b/plugins/subman/README.md
e296c6
new file mode 100644
e296c6
index 00000000..3e1cc3cd
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/README.md
e296c6
@@ -0,0 +1,56 @@
e296c6
+GNOME Settings Daemon: Subscription Manager Plugin
e296c6
+==================================================
e296c6
+
e296c6
+Testing:
e296c6
+
e296c6
+To add a test acccount on subscription.rhsm.stage.redhat.com, use Ethel:
e296c6
+http://account-manager-stage.app.eng.rdu2.redhat.com/#view
e296c6
+
e296c6
+Register with a username and password
e296c6
+-------------------------------------
e296c6
+
e296c6
+    gdbus call \
e296c6
+     --session \
e296c6
+     --dest org.gnome.SettingsDaemon.Subscription \
e296c6
+     --object-path /org/gnome/SettingsDaemon/Subscription \
e296c6
+     --method org.gnome.SettingsDaemon.Subscription.Register "{'kind':<'username'>,'hostname':<'subscription.rhsm.stage.redhat.com'>,'username':<'rhughes_test'>,'password':<'barbaz'>}"
e296c6
+
e296c6
+To register with a certificate
e296c6
+------------------------------
e296c6
+
e296c6
+    gdbus call \
e296c6
+     --session \
e296c6
+     --dest org.gnome.SettingsDaemon.Subscription \
e296c6
+     --object-path /org/gnome/SettingsDaemon/Subscription \
e296c6
+     --method org.gnome.SettingsDaemon.Subscription.Register "{'kind':<'key'>,'hostname':<'subscription.rhsm.stage.redhat.com'>,'organisation':<'foo'>,'activation-key':<'barbaz'>}"
e296c6
+
e296c6
+To unregister
e296c6
+-------------
e296c6
+
e296c6
+    gdbus call \
e296c6
+     --session \
e296c6
+     --dest org.gnome.SettingsDaemon.Subscription \
e296c6
+     --object-path /org/gnome/SettingsDaemon/Subscription \
e296c6
+     --method org.gnome.SettingsDaemon.Subscription.Unregister
e296c6
+
e296c6
+Debugging
e296c6
+---------
e296c6
+
e296c6
+Get the UNIX socket using `Subscription.Register` then call something like:
e296c6
+
e296c6
+    sudo G_MESSAGES_DEBUG=all ./plugins/subman/gsd-subman-helper \
e296c6
+     --address="unix:abstract=/var/run/dbus-ulGB1wfnbn,guid=71e6bf329d861ce366df7a1d5d036a5b" \
e296c6
+     --kind="register-with-username" \
e296c6
+     --username="rhughes_test" \
e296c6
+     --password="barbaz" \
e296c6
+     --hostname="subscription.rhsm.stage.redhat.com" \
e296c6
+     --organisation=""
e296c6
+
e296c6
+You can all see some basic debugging running `rhsmd` in the foreground:
e296c6
+
e296c6
+    sudo /usr/libexec/rhsmd -d -k
e296c6
+
e296c6
+Known Limitations
e296c6
+=================
e296c6
+
e296c6
+Proxy servers are not supported, nor are custom host ports or prefixes.
e296c6
diff --git a/plugins/subman/gsd-subman-common.c b/plugins/subman/gsd-subman-common.c
e296c6
new file mode 100644
e296c6
index 00000000..e515131e
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/gsd-subman-common.c
28c014
@@ -0,0 +1,36 @@
28c014
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
28c014
+ *
28c014
+ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
28c014
+ *
28c014
+ * This program is free software; you can redistribute it and/or modify
28c014
+ * it under the terms of the GNU General Public License as published by
28c014
+ * the Free Software Foundation; either version 2 of the License, or
28c014
+ * (at your option) any later version.
28c014
+ *
28c014
+ * This program is distributed in the hope that it will be useful,
28c014
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28c014
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28c014
+ * GNU General Public License for more details.
28c014
+ *
28c014
+ * You should have received a copy of the GNU General Public License
28c014
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
28c014
+ *
28c014
+ */
28c014
+
28c014
+#include "config.h"
28c014
+
28c014
+#include "gsd-subman-common.h"
28c014
+
28c014
+const gchar *
28c014
+gsd_subman_subscription_status_to_string (GsdSubmanSubscriptionStatus status)
28c014
+{
28c014
+	if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID)
28c014
+		return "valid";
28c014
+	if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID)
28c014
+		return "invalid";
28c014
+	if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED)
28c014
+		return "disabled";
28c014
+	if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID)
28c014
+		return "partially-valid";
28c014
+	return "unknown";
28c014
+}
e296c6
diff --git a/plugins/subman/gsd-subman-common.h b/plugins/subman/gsd-subman-common.h
e296c6
new file mode 100644
e296c6
index 00000000..fccf9f6a
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/gsd-subman-common.h
28c014
@@ -0,0 +1,40 @@
28c014
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
28c014
+ *
28c014
+ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
28c014
+ *
28c014
+ * This program is free software; you can redistribute it and/or modify
28c014
+ * it under the terms of the GNU General Public License as published by
28c014
+ * the Free Software Foundation; either version 2 of the License, or
28c014
+ * (at your option) any later version.
28c014
+ *
28c014
+ * This program is distributed in the hope that it will be useful,
28c014
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28c014
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28c014
+ * GNU General Public License for more details.
28c014
+ *
28c014
+ * You should have received a copy of the GNU General Public License
28c014
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
28c014
+ *
28c014
+ */
28c014
+
28c014
+#ifndef __GSD_SUBMAN_COMMON_H
28c014
+#define __GSD_SUBMAN_COMMON_H
28c014
+
28c014
+#include <glib-object.h>
28c014
+
28c014
+G_BEGIN_DECLS
28c014
+
28c014
+typedef enum {
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN,
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID,
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID,
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED,
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID,
28c014
+	GSD_SUBMAN_SUBSCRIPTION_STATUS_LAST
28c014
+} GsdSubmanSubscriptionStatus;
28c014
+
28c014
+const gchar	*gsd_subman_subscription_status_to_string	(GsdSubmanSubscriptionStatus	 status);
28c014
+
28c014
+G_END_DECLS
28c014
+
28c014
+#endif /* __GSD_SUBMAN_COMMON_H */
e296c6
diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
e296c6
new file mode 100644
e296c6
index 00000000..182f7190
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/gsd-subman-helper.c
28c014
@@ -0,0 +1,378 @@
28c014
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
28c014
+ *
28c014
+ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
28c014
+ *
28c014
+ * Licensed under the GNU General Public License Version 2
28c014
+ *
28c014
+ * This program is free software; you can redistribute it and/or modify
28c014
+ * it under the terms of the GNU General Public License as published by
28c014
+ * the Free Software Foundation; either version 2 of the License, or
28c014
+ * (at your option) any later version.
28c014
+ *
28c014
+ * This program is distributed in the hope that it will be useful,
28c014
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28c014
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28c014
+ * GNU General Public License for more details.
28c014
+ *
28c014
+ * You should have received a copy of the GNU General Public License
28c014
+ * along with this program; if not, write to the Free Software
28c014
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28c014
+ */
28c014
+
28c014
+#include "config.h"
28c014
+
28c014
+#include <sys/types.h>
28c014
+#include <unistd.h>
28c014
+#include <stdlib.h>
28c014
+
28c014
+#include <gio/gio.h>
28c014
+#include <json-glib/json-glib.h>
28c014
+
28c014
+static void
28c014
+_helper_convert_error (const gchar *json_txt, GError **error)
28c014
+{
28c014
+	JsonNode *json_root;
28c014
+	JsonObject *json_obj;
28c014
+	const gchar *message;
28c014
+	g_autoptr(JsonParser) json_parser = json_parser_new ();
28c014
+
28c014
+	/* this may be plain text or JSON :| */
28c014
+	if (!json_parser_load_from_data (json_parser, json_txt, -1, NULL)) {
28c014
+		g_set_error_literal (error,
28c014
+				     G_IO_ERROR,
28c014
+				     G_IO_ERROR_NOT_SUPPORTED,
28c014
+				     json_txt);
28c014
+		return;
28c014
+	}
28c014
+	json_root = json_parser_get_root (json_parser);
28c014
+	json_obj = json_node_get_object (json_root);
28c014
+	if (!json_object_has_member (json_obj, "message")) {
28c014
+		g_set_error (error,
28c014
+			     G_IO_ERROR,
28c014
+			     G_IO_ERROR_INVALID_DATA,
28c014
+			     "no message' in %s", json_txt);
28c014
+		return;
28c014
+	}
28c014
+	message = json_object_get_string_member (json_obj, "message");
28c014
+	if (g_strstr_len (message, -1, "Invalid user credentials") != NULL) {
28c014
+		g_set_error_literal (error,
28c014
+				     G_IO_ERROR,
28c014
+				     G_IO_ERROR_PERMISSION_DENIED,
28c014
+				     message);
28c014
+		return;
28c014
+	}
28c014
+	g_set_error_literal (error,
28c014
+			     G_IO_ERROR,
28c014
+			     G_IO_ERROR_NOT_SUPPORTED,
28c014
+			     message);
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_helper_unregister (GError **error)
28c014
+{
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GVariantBuilder) proxy_options = NULL;
28c014
+	g_autoptr(GVariant) res = NULL;
28c014
+
28c014
+	g_debug ("unregistering");
28c014
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
28c014
+					       NULL,
28c014
+					       "com.redhat.RHSM1",
28c014
+					       "/com/redhat/RHSM1/Unregister",
28c014
+					       "com.redhat.RHSM1.Unregister",
28c014
+					       NULL, error);
28c014
+	if (proxy == NULL) {
28c014
+		g_prefix_error (error, "Failed to get proxy: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	proxy_options = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
28c014
+	res = g_dbus_proxy_call_sync (proxy,
28c014
+				      "Unregister",
28c014
+				      g_variant_new ("(a{sv}s)",
28c014
+						     proxy_options,
28c014
+						     ""), /* lang */
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	return res != NULL;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_helper_auto_attach (GError **error)
28c014
+{
28c014
+	const gchar *str = NULL;
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GVariantBuilder) proxy_options = NULL;
28c014
+	g_autoptr(GVariant) res = NULL;
28c014
+
28c014
+	g_debug ("auto-attaching subscriptions");
28c014
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
28c014
+					       NULL,
28c014
+					       "com.redhat.RHSM1",
28c014
+					       "/com/redhat/RHSM1/Attach",
28c014
+					       "com.redhat.RHSM1.Attach",
28c014
+					       NULL, error);
28c014
+	if (proxy == NULL) {
28c014
+		g_prefix_error (error, "Failed to get proxy: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	proxy_options = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
28c014
+	res = g_dbus_proxy_call_sync (proxy,
28c014
+				      "AutoAttach",
28c014
+				      g_variant_new ("(sa{sv}s)",
28c014
+						     "", /* now? */
28c014
+						     proxy_options,
28c014
+						     ""), /* lang */
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (res == NULL)
28c014
+		return FALSE;
28c014
+	g_variant_get (res, "(&s)", &str);
28c014
+	g_debug ("Attach.AutoAttach: %s", str);
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_helper_save_config (const gchar *key, const gchar *value, GError **error)
28c014
+{
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GVariant) res = NULL;
28c014
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
28c014
+					       G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
28c014
+					       NULL,
28c014
+					       "com.redhat.RHSM1",
28c014
+					       "/com/redhat/RHSM1/Config",
28c014
+					       "com.redhat.RHSM1.Config",
28c014
+					       NULL, error);
28c014
+	if (proxy == NULL) {
28c014
+		g_prefix_error (error, "Failed to get proxy: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	res = g_dbus_proxy_call_sync (proxy, "Set",
28c014
+				      g_variant_new ("(svs)",
28c014
+						     key,
28c014
+						     g_variant_new_string (value),
28c014
+						     ""), /* lang */
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	return res != NULL;
28c014
+}
28c014
+
28c014
+int
28c014
+main (int argc, char *argv[])
28c014
+{
28c014
+	const gchar *userlang = ""; /* as root, so no translations */
28c014
+	g_autofree gchar *activation_key = NULL;
28c014
+	g_autofree gchar *address = NULL;
28c014
+	g_autofree gchar *hostname = NULL;
28c014
+	g_autofree gchar *kind = NULL;
28c014
+	g_autofree gchar *organisation = NULL;
28c014
+	g_autofree gchar *password = NULL;
28c014
+	g_autofree gchar *port = NULL;
28c014
+	g_autofree gchar *prefix = NULL;
28c014
+	g_autofree gchar *proxy_server = NULL;
28c014
+	g_autofree gchar *username = NULL;
28c014
+	g_autoptr(GDBusConnection) conn_private = NULL;
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GError) error = NULL;
28c014
+	g_autoptr(GOptionContext) context = g_option_context_new (NULL);
28c014
+	g_autoptr(GVariantBuilder) proxy_options = NULL;
28c014
+	g_autoptr(GVariantBuilder) subman_conopts = NULL;
28c014
+	g_autoptr(GVariantBuilder) subman_options = NULL;
28c014
+
28c014
+	const GOptionEntry options[] = {
28c014
+		{ "kind", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&kind, "Kind, e.g. 'username' or 'key'", NULL },
28c014
+		{ "address", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&address, "UNIX address", NULL },
28c014
+		{ "username", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&username, "Username", NULL },
28c014
+		{ "password", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&password, "Password", NULL },
28c014
+		{ "organisation", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&organisation, "Organisation", NULL },
28c014
+		{ "activation-key", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
28c014
+			&activation_key, "Activation keys", NULL },
28c014
+		{ "hostname", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
28c014
+			&hostname, "Registration server hostname", NULL },
28c014
+		{ "prefix", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
28c014
+			&prefix, "Registration server prefix", NULL },
28c014
+		{ "port", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
28c014
+			&port, "Registration server port", NULL },
28c014
+		{ "proxy", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
28c014
+			&proxy_server, "Proxy settings", NULL },
28c014
+		{ NULL}
28c014
+	};
28c014
+
28c014
+	/* check calling UID */
28c014
+	if (getuid () != 0 || geteuid () != 0) {
28c014
+		g_printerr ("This program can only be used by the root user\n");
28c014
+		return G_IO_ERROR_NOT_SUPPORTED;
28c014
+	}
28c014
+	g_option_context_add_main_entries (context, options, NULL);
28c014
+	if (!g_option_context_parse (context, &argc, &argv, &error)) {
28c014
+		g_printerr ("Failed to parse arguments: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_SUPPORTED;
28c014
+	}
28c014
+
28c014
+	/* uncommon actions */
28c014
+	if (kind == NULL) {
28c014
+		g_printerr ("No --kind specified\n");
28c014
+		return G_IO_ERROR_INVALID_DATA;
28c014
+	}
28c014
+	if (g_strcmp0 (kind, "unregister") == 0) {
28c014
+		if (!_helper_unregister (&error)) {
28c014
+			g_printerr ("Failed to Unregister: %s\n", error->message);
28c014
+			return G_IO_ERROR_NOT_INITIALIZED;
28c014
+		}
28c014
+		return EXIT_SUCCESS;
28c014
+	}
28c014
+	if (g_strcmp0 (kind, "auto-attach") == 0) {
28c014
+		if (!_helper_auto_attach (&error)) {
28c014
+			g_printerr ("Failed to AutoAttach: %s\n", error->message);
28c014
+			return G_IO_ERROR_NOT_INITIALIZED;
28c014
+		}
28c014
+		return EXIT_SUCCESS;
28c014
+	}
28c014
+
28c014
+	/* connect to abstract socket for reasons */
28c014
+	if (address == NULL) {
28c014
+		g_printerr ("No --address specified\n");
28c014
+		return G_IO_ERROR_INVALID_DATA;
28c014
+	}
28c014
+	conn_private = g_dbus_connection_new_for_address_sync (address,
28c014
+							       G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
28c014
+							       NULL, NULL,
28c014
+							       &error);
28c014
+	if (conn_private == NULL) {
28c014
+		g_printerr ("Invalid --address specified: %s\n", error->message);
28c014
+		return G_IO_ERROR_INVALID_DATA;
28c014
+	}
28c014
+	proxy = g_dbus_proxy_new_sync (conn_private,
28c014
+				       G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
28c014
+				       NULL, /* GDBusInterfaceInfo */
28c014
+				       NULL, /* name */
28c014
+				       "/com/redhat/RHSM1/Register",
28c014
+				       "com.redhat.RHSM1.Register",
28c014
+				       NULL, &error);
28c014
+	if (proxy == NULL) {
28c014
+		g_printerr ("Count not contact RHSM: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_FOUND;
28c014
+	}
28c014
+
28c014
+	/* no options */
28c014
+	subman_options = g_variant_builder_new (G_VARIANT_TYPE("a{ss}"));
28c014
+
28c014
+	/* set registration server */
28c014
+	if (hostname == NULL || hostname[0] == '\0')
28c014
+		hostname = g_strdup ("subscription.rhsm.redhat.com");
28c014
+	if (prefix == NULL || prefix[0] == '\0')
28c014
+		prefix = g_strdup ("/subscription");
28c014
+	if (port == NULL || port[0] == '\0')
28c014
+		port = g_strdup ("443");
28c014
+	subman_conopts = g_variant_builder_new (G_VARIANT_TYPE("a{ss}"));
28c014
+	g_variant_builder_add (subman_conopts, "{ss}", "host", hostname);
28c014
+	g_variant_builder_add (subman_conopts, "{ss}", "handler", prefix);
28c014
+	g_variant_builder_add (subman_conopts, "{ss}", "port", port);
28c014
+
28c014
+	/* call into RHSM */
28c014
+	if (g_strcmp0 (kind, "register-with-key") == 0) {
28c014
+		g_auto(GStrv) activation_keys = NULL;
28c014
+		g_autoptr(GError) error_local = NULL;
28c014
+		g_autoptr(GVariant) res = NULL;
28c014
+
28c014
+		if (activation_key == NULL) {
28c014
+			g_printerr ("Required --activation-key\n");
28c014
+			return G_IO_ERROR_INVALID_DATA;
28c014
+		}
28c014
+		if (organisation == NULL) {
28c014
+			g_printerr ("Required --organisation\n");
28c014
+			return G_IO_ERROR_INVALID_DATA;
28c014
+		}
28c014
+
28c014
+		g_debug ("registering using activation key");
28c014
+		activation_keys = g_strsplit (activation_key, ",", -1);
28c014
+		res = g_dbus_proxy_call_sync (proxy,
28c014
+					      "RegisterWithActivationKeys",
28c014
+					      g_variant_new ("(s^asa{ss}a{ss}s)",
28c014
+							     organisation,
28c014
+							     activation_keys,
28c014
+							     subman_options,
28c014
+							     subman_conopts,
28c014
+							     userlang),
28c014
+					      G_DBUS_CALL_FLAGS_NO_AUTO_START,
28c014
+					      -1, NULL, &error_local);
28c014
+		if (res == NULL) {
28c014
+			g_dbus_error_strip_remote_error (error_local);
28c014
+			_helper_convert_error (error_local->message, &error);
28c014
+			g_printerr ("Failed to RegisterWithActivationKeys: %s\n", error->message);
28c014
+			return error->code;
28c014
+		}
28c014
+	} else if (g_strcmp0 (kind, "register-with-username") == 0) {
28c014
+		g_autoptr(GError) error_local = NULL;
28c014
+		g_autoptr(GVariant) res = NULL;
28c014
+
28c014
+		g_debug ("registering using username and password");
28c014
+		if (username == NULL) {
28c014
+			g_printerr ("Required --username\n");
28c014
+			return G_IO_ERROR_INVALID_DATA;
28c014
+		}
28c014
+		if (password == NULL) {
28c014
+			g_printerr ("Required --password\n");
28c014
+			return G_IO_ERROR_INVALID_DATA;
28c014
+		}
28c014
+		if (organisation == NULL) {
28c014
+			g_printerr ("Required --organisation\n");
28c014
+			return G_IO_ERROR_INVALID_DATA;
28c014
+		}
28c014
+		res = g_dbus_proxy_call_sync (proxy,
28c014
+					      "Register",
28c014
+					      g_variant_new ("(sssa{ss}a{ss}s)",
28c014
+							     organisation,
28c014
+							     username,
28c014
+							     password,
28c014
+							     subman_options,
28c014
+							     subman_conopts,
28c014
+							     userlang),
28c014
+					      G_DBUS_CALL_FLAGS_NO_AUTO_START,
28c014
+					      -1, NULL, &error_local);
28c014
+		if (res == NULL) {
28c014
+			g_dbus_error_strip_remote_error (error_local);
28c014
+			_helper_convert_error (error_local->message, &error);
28c014
+			g_printerr ("Failed to Register: %s\n", error->message);
28c014
+			return error->code;
28c014
+		}
28c014
+	} else {
28c014
+		g_printerr ("Invalid --kind specified: %s\n", kind);
28c014
+		return G_IO_ERROR_INVALID_DATA;
28c014
+	}
28c014
+
28c014
+	/* set the new hostname */
28c014
+	if (!_helper_save_config ("server.hostname", hostname, &error)) {
28c014
+		g_printerr ("Failed to save hostname: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_INITIALIZED;
28c014
+	}
28c014
+	if (!_helper_save_config ("server.prefix", prefix, &error)) {
28c014
+		g_printerr ("Failed to save prefix: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_INITIALIZED;
28c014
+	}
28c014
+	if (!_helper_save_config ("server.port", port, &error)) {
28c014
+		g_printerr ("Failed to save port: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_INITIALIZED;
28c014
+	}
28c014
+
28c014
+	/* wait for rhsmd to notice the new config */
28c014
+	g_usleep (G_USEC_PER_SEC * 5);
28c014
+
28c014
+	/* auto-attach */
28c014
+	if (!_helper_auto_attach (&error)) {
28c014
+		g_printerr ("Failed to AutoAttach: %s\n", error->message);
28c014
+		return G_IO_ERROR_NOT_INITIALIZED;
28c014
+	}
28c014
+
28c014
+	return EXIT_SUCCESS;
28c014
+}
e296c6
diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
e296c6
new file mode 100644
e296c6
index 00000000..08b13fa6
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/gsd-subscription-manager.c
e296c6
@@ -0,0 +1,982 @@
28c014
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
28c014
+ *
28c014
+ * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
28c014
+ *
28c014
+ * This program is free software; you can redistribute it and/or modify
28c014
+ * it under the terms of the GNU General Public License as published by
28c014
+ * the Free Software Foundation; either version 2 of the License, or
28c014
+ * (at your option) any later version.
28c014
+ *
28c014
+ * This program is distributed in the hope that it will be useful,
28c014
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28c014
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28c014
+ * GNU General Public License for more details.
28c014
+ *
28c014
+ * You should have received a copy of the GNU General Public License
28c014
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
28c014
+ *
28c014
+ */
28c014
+
28c014
+#include "config.h"
28c014
+
28c014
+#include <glib/gi18n.h>
28c014
+#include <gdk/gdk.h>
28c014
+#include <gtk/gtk.h>
28c014
+#include <json-glib/json-glib.h>
28c014
+#include <libnotify/notify.h>
28c014
+
28c014
+#include "gnome-settings-profile.h"
28c014
+#include "gsd-subman-common.h"
28c014
+#include "gsd-subscription-manager.h"
28c014
+
28c014
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
28c014
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
28c014
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
28c014
+
28c014
+#define GSD_SUBSCRIPTION_DBUS_NAME		GSD_DBUS_NAME ".Subscription"
28c014
+#define GSD_SUBSCRIPTION_DBUS_PATH		GSD_DBUS_PATH "/Subscription"
28c014
+#define GSD_SUBSCRIPTION_DBUS_INTERFACE		GSD_DBUS_BASE_INTERFACE ".Subscription"
28c014
+
28c014
+static const gchar introspection_xml[] =
28c014
+"<node>"
28c014
+"  <interface name='org.gnome.SettingsDaemon.Subscription'>"
28c014
+"    <method name='Register'>"
28c014
+"      <arg type='a{sv}' name='options' direction='in'/>"
28c014
+"    </method>"
28c014
+"    <method name='Unregister'/>"
28c014
+"    <property name='SubscriptionStatus' type='u' access='read'/>"
28c014
+"  </interface>"
28c014
+"</node>";
28c014
+
28c014
+#define GSD_SUBSCRIPTION_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerPrivate))
28c014
+
28c014
+typedef enum {
28c014
+	_RHSM_INTERFACE_CONFIG,
28c014
+	_RHSM_INTERFACE_REGISTER_SERVER,
28c014
+	_RHSM_INTERFACE_ATTACH,
28c014
+	_RHSM_INTERFACE_ENTITLEMENT,
28c014
+	_RHSM_INTERFACE_PRODUCTS,
28c014
+	_RHSM_INTERFACE_CONSUMER,
28c014
+	_RHSM_INTERFACE_SYSPURPOSE,
28c014
+	_RHSM_INTERFACE_LAST
28c014
+} _RhsmInterface;
28c014
+
28c014
+struct GsdSubscriptionManagerPrivate
28c014
+{
28c014
+	/* D-Bus */
28c014
+	guint		 name_id;
28c014
+	GDBusNodeInfo	*introspection_data;
28c014
+	GDBusConnection	*connection;
28c014
+	GCancellable	*bus_cancellable;
28c014
+
28c014
+	GDBusProxy	*proxies[_RHSM_INTERFACE_LAST];
28c014
+	const gchar	*userlang;	/* owned by GLib internally */
28c014
+	GHashTable	*config; 	/* str:str */
28c014
+	gchar		*address;
28c014
+
28c014
+	GTimer		*timer_last_notified;
28c014
+	NotifyNotification	*notification_expired;
28c014
+	NotifyNotification	*notification_registered;
28c014
+	NotifyNotification	*notification_registration_required;
28c014
+	GsdSubmanSubscriptionStatus	 subscription_status;
28c014
+	GsdSubmanSubscriptionStatus	 subscription_status_last;
28c014
+};
28c014
+
28c014
+enum {
28c014
+	PROP_0,
28c014
+};
28c014
+
28c014
+static void     gsd_subscription_manager_class_init  (GsdSubscriptionManagerClass *klass);
28c014
+static void     gsd_subscription_manager_init        (GsdSubscriptionManager      *subscription_manager);
28c014
+static void     gsd_subscription_manager_finalize    (GObject             *object);
28c014
+
28c014
+G_DEFINE_TYPE (GsdSubscriptionManager, gsd_subscription_manager, G_TYPE_OBJECT)
28c014
+
28c014
+static gpointer manager_object = NULL;
28c014
+
28c014
+GQuark
28c014
+gsd_subscription_manager_error_quark (void)
28c014
+{
28c014
+	static GQuark quark = 0;
28c014
+	if (!quark)
28c014
+		quark = g_quark_from_static_string ("gsd_subscription_manager_error");
28c014
+	return quark;
28c014
+}
28c014
+
28c014
+static GsdSubmanSubscriptionStatus
28c014
+_client_subscription_status_from_text (const gchar *status_txt)
28c014
+{
28c014
+	if (g_strcmp0 (status_txt, "Unknown") == 0)
28c014
+		return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
28c014
+	if (g_strcmp0 (status_txt, "Current") == 0)
28c014
+		return GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID;
28c014
+	if (g_strcmp0 (status_txt, "Invalid") == 0)
28c014
+		return GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
28c014
+	if (g_strcmp0 (status_txt, "Disabled") == 0)
28c014
+		return GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED;
28c014
+	if (g_strcmp0 (status_txt, "Insufficient") == 0)
28c014
+		return GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID;
28c014
+	g_warning ("Unknown subscription status: %s", status_txt); // 'Current'?
28c014
+	return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
28c014
+}
28c014
+
e296c6
+static void
e296c6
+_emit_property_changed (GsdSubscriptionManager *manager,
e296c6
+		        const gchar *property_name,
e296c6
+		        GVariant *property_value)
e296c6
+{
e296c6
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
e296c6
+	GVariantBuilder builder;
e296c6
+	GVariantBuilder invalidated_builder;
e296c6
+
e296c6
+	/* not yet connected */
e296c6
+	if (priv->connection == NULL)
e296c6
+		return;
e296c6
+
e296c6
+	/* build the dict */
e296c6
+	g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
e296c6
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
e296c6
+	g_variant_builder_add (&builder,
e296c6
+			       "{sv}",
e296c6
+			       property_name,
e296c6
+			       property_value);
e296c6
+	g_dbus_connection_emit_signal (priv->connection,
e296c6
+				       NULL,
e296c6
+				       GSD_SUBSCRIPTION_DBUS_PATH,
e296c6
+				       "org.freedesktop.DBus.Properties",
e296c6
+				       "PropertiesChanged",
e296c6
+				       g_variant_new ("(sa{sv}as)",
e296c6
+				       GSD_SUBSCRIPTION_DBUS_INTERFACE,
e296c6
+				       &builder,
e296c6
+				       &invalidated_builder),
e296c6
+				       NULL);
e296c6
+	g_variant_builder_clear (&builder);
e296c6
+	g_variant_builder_clear (&invalidated_builder);
e296c6
+}
e296c6
+
28c014
+static gboolean
28c014
+_client_subscription_status_update (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	JsonNode *json_root;
28c014
+	JsonObject *json_obj;
28c014
+	const gchar *json_txt = NULL;
28c014
+	const gchar *status_txt = NULL;
28c014
+	g_autoptr(GVariant) val = NULL;
28c014
+	g_autoptr(JsonParser) json_parser = json_parser_new ();
28c014
+
28c014
+	/* save old value */
28c014
+	priv->subscription_status_last = priv->subscription_status;
28c014
+
28c014
+	val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_ENTITLEMENT],
28c014
+				      "GetStatus",
28c014
+				      g_variant_new ("(ss)",
28c014
+						     "", /* assumed as 'now' */
28c014
+						     priv->userlang),
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (val == NULL)
28c014
+		return FALSE;
28c014
+	g_variant_get (val, "(&s)", &json_txt);
28c014
+	g_debug ("Entitlement.GetStatus JSON: %s", json_txt);
28c014
+	if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
28c014
+		return FALSE;
28c014
+	json_root = json_parser_get_root (json_parser);
28c014
+	json_obj = json_node_get_object (json_root);
28c014
+	if (!json_object_has_member (json_obj, "status")) {
28c014
+		g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
28c014
+			     "no Entitlement.GetStatus status in %s", json_txt);
28c014
+		return FALSE;
28c014
+	}
28c014
+
28c014
+	status_txt = json_object_get_string_member (json_obj, "status");
28c014
+	g_debug ("Entitlement.GetStatus: %s", status_txt);
28c014
+	priv->subscription_status = _client_subscription_status_from_text (status_txt);
e296c6
+
e296c6
+	/* emit notification for g-c-c */
e296c6
+	if (priv->subscription_status != priv->subscription_status_last) {
e296c6
+		_emit_property_changed (manager, "SubscriptionStatus",
e296c6
+				       g_variant_new_uint32 (priv->subscription_status));
e296c6
+	}
e296c6
+
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_syspurpose_update (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	JsonNode *json_root;
28c014
+	JsonObject *json_obj;
28c014
+	const gchar *json_txt = NULL;
28c014
+	g_autoptr(GVariant) val = NULL;
28c014
+	g_autoptr(JsonParser) json_parser = json_parser_new ();
28c014
+
28c014
+	val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_SYSPURPOSE],
28c014
+				      "GetSyspurpose",
28c014
+				      g_variant_new ("(s)", priv->userlang),
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (val == NULL)
28c014
+		return FALSE;
28c014
+	g_variant_get (val, "(&s)", &json_txt);
28c014
+	g_debug ("Syspurpose.GetSyspurpose JSON: %s", json_txt);
28c014
+	if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
28c014
+		return FALSE;
28c014
+	json_root = json_parser_get_root (json_parser);
28c014
+	json_obj = json_node_get_object (json_root);
28c014
+	if (!json_object_has_member (json_obj, "status")) {
28c014
+		g_debug ("Syspurpose.GetSyspurpose: Unknown");
28c014
+		return TRUE;
28c014
+	}
28c014
+	g_debug ("Syspurpose.GetSyspurpose: '%s", json_object_get_string_member (json_obj, "status"));
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_register_start (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	const gchar *address = NULL;
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GVariant) val = NULL;
28c014
+
28c014
+	/* already started */
28c014
+	if (priv->address != NULL)
28c014
+		return TRUE;
28c014
+
28c014
+	/* apparently: "we can't send registration credentials over the regular
28c014
+	 * system or session bus since those aren't really locked down..." */
28c014
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+					       G_DBUS_PROXY_FLAGS_NONE,
28c014
+					       NULL,
28c014
+					       "com.redhat.RHSM1",
28c014
+					       "/com/redhat/RHSM1/RegisterServer",
28c014
+					       "com.redhat.RHSM1.RegisterServer",
28c014
+					       NULL, error);
28c014
+	if (proxy == NULL)
28c014
+		return FALSE;
28c014
+	val = g_dbus_proxy_call_sync (proxy, "Start",
28c014
+				      g_variant_new ("(s)", priv->userlang),
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (val == NULL)
28c014
+		return FALSE;
28c014
+	g_variant_get (val, "(&s)", &address);
28c014
+	g_debug ("RegisterServer.Start: %s", address);
28c014
+	priv->address = g_strdup (address);
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_register_stop (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	g_autoptr(GDBusProxy) proxy = NULL;
28c014
+	g_autoptr(GVariant) val = NULL;
28c014
+
28c014
+	/* already started */
28c014
+	if (priv->address == NULL)
28c014
+		return TRUE;
28c014
+
28c014
+	/* stop registration server */
28c014
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+					       G_DBUS_PROXY_FLAGS_NONE,
28c014
+					       NULL,
28c014
+					       "com.redhat.RHSM1",
28c014
+					       "/com/redhat/RHSM1/RegisterServer",
28c014
+					       "com.redhat.RHSM1.RegisterServer",
28c014
+					       NULL, error);
28c014
+	if (proxy == NULL)
28c014
+		return FALSE;
28c014
+	val = g_dbus_proxy_call_sync (proxy, "Stop",
28c014
+				      g_variant_new ("(s)", priv->userlang),
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (val == NULL)
28c014
+		return FALSE;
28c014
+	g_clear_pointer (&priv->address, g_free);
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_subprocess_wait_check (GSubprocess *subprocess, GError **error)
28c014
+{
28c014
+	gint rc;
28c014
+	if (!g_subprocess_wait (subprocess, NULL, error)) {
28c014
+		g_prefix_error (error, "failed to run pkexec: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	rc = g_subprocess_get_exit_status (subprocess);
28c014
+	if (rc != 0) {
28c014
+		GInputStream *istream = g_subprocess_get_stderr_pipe (subprocess);
28c014
+		gchar buf[1024] = { 0x0 };
28c014
+		gsize sz = 0;
28c014
+		g_input_stream_read_all (istream, buf, sizeof(buf) - 1, &sz, NULL, NULL);
28c014
+		if (sz == 0) {
28c014
+			g_set_error_literal (error, G_IO_ERROR, rc,
28c014
+					     "Failed to run helper without stderr");
28c014
+			return FALSE;
28c014
+		}
28c014
+		g_set_error_literal (error, G_IO_ERROR, rc, buf);
28c014
+		return FALSE;
28c014
+	}
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+typedef enum {
28c014
+	_NOTIFY_EXPIRED,
28c014
+	_NOTIFY_REGISTRATION_REQUIRED,
28c014
+	_NOTIFY_REGISTERED
28c014
+} _NotifyKind;
28c014
+
28c014
+static void
28c014
+_show_notification (GsdSubscriptionManager *manager, _NotifyKind notify_kind)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	switch (notify_kind) {
28c014
+	case _NOTIFY_EXPIRED:
28c014
+		notify_notification_close (priv->notification_registered, NULL);
28c014
+		notify_notification_close (priv->notification_registration_required, NULL);
28c014
+		notify_notification_show (priv->notification_expired, NULL);
28c014
+		break;
28c014
+	case _NOTIFY_REGISTRATION_REQUIRED:
28c014
+		notify_notification_close (priv->notification_registered, NULL);
28c014
+		notify_notification_close (priv->notification_expired, NULL);
28c014
+		notify_notification_show (priv->notification_registration_required, NULL);
28c014
+		break;
28c014
+	case _NOTIFY_REGISTERED:
28c014
+		notify_notification_close (priv->notification_expired, NULL);
28c014
+		notify_notification_close (priv->notification_registration_required, NULL);
28c014
+		notify_notification_show (priv->notification_registered, NULL);
28c014
+		break;
28c014
+	default:
28c014
+		break;
28c014
+	}
28c014
+	g_timer_reset (priv->timer_last_notified);
28c014
+}
28c014
+
28c014
+static void
28c014
+_client_maybe__show_notification (GsdSubscriptionManager *manager)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+
28c014
+	/* startup */
28c014
+	if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
28c014
+	    priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
28c014
+		_show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
28c014
+		return;
28c014
+	}
28c014
+
28c014
+	/* something changed */
28c014
+	if (priv->subscription_status_last != priv->subscription_status) {
28c014
+		g_debug ("transisition from subscription status '%s' to '%s'",
28c014
+			 gsd_subman_subscription_status_to_string (priv->subscription_status_last),
28c014
+			 gsd_subman_subscription_status_to_string (priv->subscription_status));
28c014
+
28c014
+		/* needs registration */
28c014
+		if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
28c014
+		    priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID) {
28c014
+			_show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
28c014
+			return;
28c014
+		}
28c014
+
28c014
+		/* was unregistered */
28c014
+		if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
28c014
+		    priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
28c014
+			_show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
28c014
+			return;
28c014
+		}
28c014
+
28c014
+		/* registered */
28c014
+		if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
28c014
+		    priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
28c014
+		    g_timer_elapsed (priv->timer_last_notified, NULL) > 60) {
28c014
+			_show_notification (manager, _NOTIFY_REGISTERED);
28c014
+			return;
28c014
+		}
28c014
+	}
28c014
+
28c014
+	/* nag again */
28c014
+	if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
28c014
+	    g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
28c014
+		_show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
28c014
+		return;
28c014
+	}
28c014
+	if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID &&
28c014
+	    g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
28c014
+		_show_notification (manager, _NOTIFY_EXPIRED);
28c014
+		return;
28c014
+	}
28c014
+	if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID &&
28c014
+	    g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
28c014
+		_show_notification (manager, _NOTIFY_EXPIRED);
28c014
+		return;
28c014
+	}
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_register_with_keys (GsdSubscriptionManager *manager,
28c014
+				  const gchar *hostname,
28c014
+				  const gchar *organisation,
28c014
+				  const gchar *activation_key,
28c014
+				  GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	g_autoptr(GSubprocess) subprocess = NULL;
28c014
+
28c014
+	/* apparently: "we can't send registration credentials over the regular
28c014
+	 * system or session bus since those aren't really locked down..." */
28c014
+	if (!_client_register_start (manager, error))
28c014
+		return FALSE;
28c014
+	g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
28c014
+	subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
28c014
+				       "pkexec", LIBEXECDIR "/gsd-subman-helper",
28c014
+				       "--kind", "register-with-key",
28c014
+				       "--address", priv->address,
28c014
+				       "--hostname", hostname,
28c014
+				       "--organisation", organisation,
28c014
+				       "--activation-key", activation_key,
28c014
+				       NULL);
28c014
+	if (subprocess == NULL) {
28c014
+		g_prefix_error (error, "failed to find pkexec: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	if (!_client_subprocess_wait_check (subprocess, error))
28c014
+		return FALSE;
28c014
+
28c014
+	/* FIXME: also do on error? */
28c014
+	if (!_client_register_stop (manager, error))
28c014
+		return FALSE;
28c014
+	if (!_client_subscription_status_update (manager, error))
28c014
+		return FALSE;
28c014
+	_client_maybe__show_notification (manager);
28c014
+
28c014
+	/* success */
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_register (GsdSubscriptionManager *manager,
28c014
+			 const gchar *hostname,
28c014
+			 const gchar *organisation,
28c014
+			 const gchar *username,
28c014
+			 const gchar *password,
28c014
+			 GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	g_autoptr(GSubprocess) subprocess = NULL;
28c014
+
28c014
+	/* fallback */
28c014
+	if (organisation == NULL)
28c014
+		organisation = "";
28c014
+
28c014
+	/* apparently: "we can't send registration credentials over the regular
28c014
+	 * system or session bus since those aren't really locked down..." */
28c014
+	if (!_client_register_start (manager, error))
28c014
+		return FALSE;
28c014
+	g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
28c014
+	subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
28c014
+				       "pkexec", LIBEXECDIR "/gsd-subman-helper",
28c014
+				       "--kind", "register-with-username",
28c014
+				       "--address", priv->address,
28c014
+				       "--hostname", hostname,
28c014
+				       "--organisation", organisation,
28c014
+				       "--username", username,
28c014
+				       "--password", password,
28c014
+				       NULL);
28c014
+	if (subprocess == NULL) {
28c014
+		g_prefix_error (error, "failed to find pkexec: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	if (!_client_subprocess_wait_check (subprocess, error))
28c014
+		return FALSE;
28c014
+
28c014
+	/* FIXME: also do on error? */
28c014
+	if (!_client_register_stop (manager, error))
28c014
+		return FALSE;
28c014
+	if (!_client_subscription_status_update (manager, error))
28c014
+		return FALSE;
28c014
+	_client_maybe__show_notification (manager);
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_unregister (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	g_autoptr(GSubprocess) subprocess = NULL;
28c014
+
28c014
+	/* apparently: "we can't send registration credentials over the regular
28c014
+	 * system or session bus since those aren't really locked down..." */
28c014
+	if (!_client_register_start (manager, error))
28c014
+		return FALSE;
28c014
+	g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
28c014
+	subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
28c014
+				       "pkexec", LIBEXECDIR "/gsd-subman-helper",
28c014
+				       "--kind", "unregister",
28c014
+				       NULL);
28c014
+	if (subprocess == NULL) {
28c014
+		g_prefix_error (error, "failed to find pkexec: ");
28c014
+		return FALSE;
28c014
+	}
28c014
+	if (!_client_subprocess_wait_check (subprocess, error))
28c014
+		return FALSE;
28c014
+	if (!_client_subscription_status_update (manager, error))
28c014
+		return FALSE;
28c014
+	_client_maybe__show_notification (manager);
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_update_config (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	g_autoptr(GVariant) val = NULL;
28c014
+	g_autoptr(GVariant) val_server = NULL;
28c014
+	g_autoptr(GVariantDict) dict = NULL;
28c014
+	GVariantIter iter;
28c014
+	gchar *key;
28c014
+	gchar *value;
28c014
+
28c014
+	val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_CONFIG],
28c014
+				      "GetAll",
28c014
+				      g_variant_new ("(s)", priv->userlang),
28c014
+				      G_DBUS_CALL_FLAGS_NONE,
28c014
+				      -1, NULL, error);
28c014
+	if (val == NULL)
28c014
+		return FALSE;
28c014
+	dict = g_variant_dict_new (g_variant_get_child_value (val, 0));
28c014
+	val_server = g_variant_dict_lookup_value (dict, "server", G_VARIANT_TYPE("a{ss}"));
28c014
+	if (val_server != NULL) {
28c014
+		g_variant_iter_init (&iter, val_server);
28c014
+		while (g_variant_iter_next (&iter, "{ss}", &key, &value)) {
28c014
+			g_debug ("%s=%s", key, value);
28c014
+			g_hash_table_insert (priv->config,
28c014
+					     g_steal_pointer (&key),
28c014
+					     g_steal_pointer (&value));
28c014
+		}
28c014
+	}
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+static void
28c014
+_subman_proxy_signal_cb (GDBusProxy *proxy,
28c014
+			 const gchar *sender_name,
28c014
+			 const gchar *signal_name,
28c014
+			 GVariant *parameters,
28c014
+			 GsdSubscriptionManager *manager)
28c014
+{
28c014
+	g_autoptr(GError) error = NULL;
28c014
+	if (!_client_syspurpose_update (manager, &error)) {
28c014
+		g_warning ("failed to update syspurpose: %s", error->message);
28c014
+		g_clear_error (&error);
28c014
+	}
28c014
+	if (!_client_subscription_status_update (manager, &error)) {
28c014
+		g_warning ("failed to update subscription status: %s", error->message);
28c014
+		g_clear_error (&error);
28c014
+	}
28c014
+	_client_maybe__show_notification (manager);
28c014
+}
28c014
+
28c014
+static void
28c014
+_client_unload (GsdSubscriptionManager *manager)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	for (guint i = 0; i < _RHSM_INTERFACE_LAST; i++)
28c014
+		g_clear_object (&priv->proxies[i]);
28c014
+	g_hash_table_unref (priv->config);
28c014
+}
28c014
+
28c014
+static const gchar *
28c014
+_rhsm_interface_to_string (_RhsmInterface kind)
28c014
+{
28c014
+	if (kind == _RHSM_INTERFACE_CONFIG)
28c014
+		return "Config";
28c014
+	if (kind == _RHSM_INTERFACE_REGISTER_SERVER)
28c014
+		return "RegisterServer";
28c014
+	if (kind == _RHSM_INTERFACE_ATTACH)
28c014
+		return "Attach";
28c014
+	if (kind == _RHSM_INTERFACE_ENTITLEMENT)
28c014
+		return "Entitlement";
28c014
+	if (kind == _RHSM_INTERFACE_PRODUCTS)
28c014
+		return "Products";
28c014
+	if (kind == _RHSM_INTERFACE_CONSUMER)
28c014
+		return "Consumer";
28c014
+	if (kind == _RHSM_INTERFACE_SYSPURPOSE)
28c014
+		return "Syspurpose";
28c014
+	return NULL;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+_client_load (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+
28c014
+	priv->config = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
28c014
+
28c014
+	/* connect to all the interfaces on the *different* objects :| */
28c014
+	for (guint i = 0; i < _RHSM_INTERFACE_LAST; i++) {
28c014
+		const gchar *kind = _rhsm_interface_to_string (i);
28c014
+		g_autofree gchar *opath = g_strdup_printf ("/com/redhat/RHSM1/%s", kind);
28c014
+		g_autofree gchar *iface = g_strdup_printf ("com.redhat.RHSM1.%s", kind);
28c014
+		priv->proxies[i] =
28c014
+			g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
28c014
+						       G_DBUS_PROXY_FLAGS_NONE,
28c014
+						       NULL,
28c014
+						       "com.redhat.RHSM1",
28c014
+						       opath, iface,
28c014
+						       NULL,
28c014
+						       error);
28c014
+		if (priv->proxies[i] == NULL)
28c014
+			return FALSE;
28c014
+		/* we want to get notified if the status of the system changes */
28c014
+		g_signal_connect (priv->proxies[i], "g-signal",
28c014
+				  G_CALLBACK (_subman_proxy_signal_cb), manager);
28c014
+	}
28c014
+
28c014
+	/* get initial status */
28c014
+	priv->userlang = "";
28c014
+	if (!_client_update_config (manager, error))
28c014
+		return FALSE;
28c014
+	if (!_client_subscription_status_update (manager, error))
28c014
+		return FALSE;
28c014
+	if (!_client_syspurpose_update (manager, error))
28c014
+		return FALSE;
28c014
+
28c014
+	/* success */
28c014
+	return TRUE;
28c014
+}
28c014
+
28c014
+gboolean
28c014
+gsd_subscription_manager_start (GsdSubscriptionManager *manager, GError **error)
28c014
+{
28c014
+	gboolean ret;
28c014
+	g_debug ("Starting subscription manager");
28c014
+	gnome_settings_profile_start (NULL);
28c014
+	ret = _client_load (manager, error);
28c014
+	_client_maybe__show_notification (manager);
28c014
+	gnome_settings_profile_end (NULL);
28c014
+	return ret;
28c014
+}
28c014
+
28c014
+void
28c014
+gsd_subscription_manager_stop (GsdSubscriptionManager *manager)
28c014
+{
28c014
+	g_debug ("Stopping subscription manager");
28c014
+	_client_unload (manager);
28c014
+}
28c014
+
28c014
+static void
28c014
+gsd_subscription_manager_class_init (GsdSubscriptionManagerClass *klass)
28c014
+{
28c014
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
28c014
+	object_class->finalize = gsd_subscription_manager_finalize;
28c014
+        notify_init ("gnome-settings-daemon");
28c014
+	g_type_class_add_private (klass, sizeof (GsdSubscriptionManagerPrivate));
28c014
+}
28c014
+
28c014
+static void
28c014
+_launch_info_overview (void)
28c014
+{
28c014
+	const gchar *argv[] = { "gnome-control-center", "info-overview", NULL };
28c014
+	g_debug ("Running gnome-control-center info-overview");
28c014
+	g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
28c014
+		       NULL, NULL, NULL, NULL);
28c014
+}
28c014
+
28c014
+static void
28c014
+_notify_closed_cb (NotifyNotification *notification, gpointer user_data)
28c014
+{
28c014
+	/* FIXME: only launch when clicking on the main body, not the window close */
28c014
+	if (notify_notification_get_closed_reason (notification) == 0x400)
28c014
+		_launch_info_overview ();
28c014
+}
28c014
+
28c014
+static void
28c014
+_notify_clicked_cb (NotifyNotification *notification, char *action, gpointer user_data)
28c014
+{
28c014
+	_launch_info_overview ();
28c014
+}
28c014
+
28c014
+static void
28c014
+gsd_subscription_manager_init (GsdSubscriptionManager *manager)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv = GSD_SUBSCRIPTION_MANAGER_GET_PRIVATE (manager);
28c014
+
28c014
+	priv->timer_last_notified = g_timer_new ();
28c014
+
28c014
+	/* expired */
28c014
+	priv->notification_expired =
28c014
+		notify_notification_new (_("Subscription Has Expired"),
28c014
+					 _("Add or renew a subscription to continue receiving software updates."),
28c014
+					 NULL);
28c014
+	notify_notification_set_app_name (priv->notification_expired, _("Subscription"));
28c014
+	notify_notification_set_hint_string (priv->notification_expired, "desktop-entry", "subman-panel");
28c014
+	notify_notification_set_hint_string (priv->notification_expired, "x-gnome-privacy-scope", "system");
28c014
+	notify_notification_set_urgency (priv->notification_expired, NOTIFY_URGENCY_CRITICAL);
28c014
+	notify_notification_add_action (priv->notification_expired,
28c014
+					"info-overview", _("Subscribe System…"),
28c014
+					_notify_clicked_cb,
28c014
+					manager, NULL);
28c014
+	g_signal_connect (priv->notification_expired, "closed",
28c014
+			  G_CALLBACK (_notify_closed_cb), manager);
28c014
+
28c014
+	/* registered */
28c014
+	priv->notification_registered =
28c014
+		notify_notification_new (_("Registration Successful"),
28c014
+					 _("The system has been registered and software updates have been enabled."),
28c014
+					 NULL);
28c014
+	notify_notification_set_app_name (priv->notification_registered, _("Subscription"));
28c014
+	notify_notification_set_hint_string (priv->notification_registered, "desktop-entry", "subman-panel");
28c014
+	notify_notification_set_hint_string (priv->notification_registered, "x-gnome-privacy-scope", "system");
28c014
+	notify_notification_set_urgency (priv->notification_registered, NOTIFY_URGENCY_CRITICAL);
28c014
+	g_signal_connect (priv->notification_registered, "closed",
28c014
+			  G_CALLBACK (_notify_closed_cb), manager);
28c014
+
28c014
+	/* registration required */
28c014
+	priv->notification_registration_required =
28c014
+		notify_notification_new (_("System Not Registered"),
28c014
+					 _("Please register your system to receive software updates."),
28c014
+					 NULL);
28c014
+	notify_notification_set_app_name (priv->notification_registration_required, _("Subscription"));
28c014
+	notify_notification_set_hint_string (priv->notification_registration_required, "desktop-entry", "subman-panel");
28c014
+	notify_notification_set_hint_string (priv->notification_registration_required, "x-gnome-privacy-scope", "system");
28c014
+	notify_notification_set_urgency (priv->notification_registration_required, NOTIFY_URGENCY_CRITICAL);
28c014
+	notify_notification_add_action (priv->notification_registration_required,
28c014
+					"info-overview", _("Register System…"),
28c014
+					_notify_clicked_cb,
28c014
+					manager, NULL);
28c014
+	g_signal_connect (priv->notification_registration_required, "closed",
28c014
+			  G_CALLBACK (_notify_closed_cb), manager);
28c014
+}
28c014
+
28c014
+static void
28c014
+gsd_subscription_manager_finalize (GObject *object)
28c014
+{
28c014
+	GsdSubscriptionManager *manager;
28c014
+
28c014
+	g_return_if_fail (object != NULL);
28c014
+	g_return_if_fail (GSD_IS_SUBSCRIPTION_MANAGER (object));
28c014
+
28c014
+	manager = GSD_SUBSCRIPTION_MANAGER (object);
28c014
+
28c014
+	gsd_subscription_manager_stop (manager);
28c014
+
28c014
+	if (manager->priv->bus_cancellable != NULL) {
28c014
+		g_cancellable_cancel (manager->priv->bus_cancellable);
28c014
+		g_clear_object (&manager->priv->bus_cancellable);
28c014
+	}
28c014
+
28c014
+	g_clear_pointer (&manager->priv->introspection_data, g_dbus_node_info_unref);
28c014
+	g_clear_object (&manager->priv->connection);
28c014
+	g_clear_object (&manager->priv->notification_expired);
28c014
+	g_clear_object (&manager->priv->notification_registered);
28c014
+	g_timer_destroy (manager->priv->timer_last_notified);
28c014
+
28c014
+	if (manager->priv->name_id != 0) {
28c014
+		g_bus_unown_name (manager->priv->name_id);
28c014
+		manager->priv->name_id = 0;
28c014
+	}
28c014
+
28c014
+	G_OBJECT_CLASS (gsd_subscription_manager_parent_class)->finalize (object);
28c014
+}
28c014
+
28c014
+static void
28c014
+handle_method_call (GDBusConnection       *connection,
28c014
+		    const gchar           *sender,
28c014
+		    const gchar           *object_path,
28c014
+		    const gchar           *interface_name,
28c014
+		    const gchar           *method_name,
28c014
+		    GVariant              *parameters,
28c014
+		    GDBusMethodInvocation *invocation,
28c014
+		    gpointer               user_data)
28c014
+{
28c014
+	GsdSubscriptionManager *manager = GSD_SUBSCRIPTION_MANAGER (user_data);
28c014
+	g_autoptr(GError) error = NULL;
28c014
+
28c014
+	if (g_strcmp0 (method_name, "Register") == 0) {
28c014
+		const gchar *organisation = NULL;
28c014
+		const gchar *hostname = NULL;
28c014
+
28c014
+		if (FALSE) {
28c014
+			g_dbus_method_invocation_return_error_literal (invocation,
28c014
+								       G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
28c014
+								       "Cannot register at this time");
28c014
+
28c014
+			return;
28c014
+		}
28c014
+
28c014
+		g_autoptr(GVariantDict) dict = g_variant_dict_new (g_variant_get_child_value (parameters, 0));
28c014
+
28c014
+		const gchar *kind = NULL;
28c014
+		if (!g_variant_dict_lookup (dict, "kind", "&s", &kind)) {
28c014
+			g_dbus_method_invocation_return_error_literal (invocation,
28c014
+								       G_IO_ERROR, G_IO_ERROR_FAILED,
28c014
+								       "No kind specified");
28c014
+
28c014
+			return;
28c014
+		}
28c014
+		if (g_strcmp0 (kind, "username") == 0) {
28c014
+			const gchar *username = NULL;
28c014
+			const gchar *password = NULL;
28c014
+			g_variant_dict_lookup (dict, "hostname", "&s", &hostname);
28c014
+			g_variant_dict_lookup (dict, "organisation", "&s", &organisation);
28c014
+			g_variant_dict_lookup (dict, "username", "&s", &username);
28c014
+			g_variant_dict_lookup (dict, "password", "&s", &password);
28c014
+			if (!_client_register (manager,
28c014
+						     hostname,
28c014
+						     organisation,
28c014
+						     username,
28c014
+						     password,
28c014
+						     &error)) {
28c014
+				g_dbus_method_invocation_return_gerror (invocation, error);
28c014
+				return;
28c014
+			}
28c014
+		} else if (g_strcmp0 (kind, "key") == 0) {
28c014
+			const gchar *activation_key = NULL;
28c014
+			g_variant_dict_lookup (dict, "hostname", "&s", &hostname);
28c014
+			g_variant_dict_lookup (dict, "organisation", "&s", &organisation);
28c014
+			g_variant_dict_lookup (dict, "activation-key", "&s", &activation_key);
28c014
+			if (!_client_register_with_keys (manager,
28c014
+							       hostname,
28c014
+							       organisation,
28c014
+							       activation_key,
28c014
+							       &error)) {
28c014
+				g_dbus_method_invocation_return_gerror (invocation, error);
28c014
+				return;
28c014
+			}
28c014
+		} else {
28c014
+			g_dbus_method_invocation_return_error_literal (invocation,
28c014
+								       G_IO_ERROR, G_IO_ERROR_FAILED,
28c014
+								       "Invalid kind specified");
28c014
+
28c014
+			return;
28c014
+		}
28c014
+		g_dbus_method_invocation_return_value (invocation, NULL);
28c014
+	} else if (g_strcmp0 (method_name, "Unregister") == 0) {
28c014
+		if (!_client_unregister (manager, &error)) {
28c014
+			g_dbus_method_invocation_return_gerror (invocation, error);
28c014
+			return;
28c014
+		}
28c014
+		g_dbus_method_invocation_return_value (invocation, NULL);
28c014
+	} else {
28c014
+		g_assert_not_reached ();
28c014
+	}
28c014
+}
28c014
+
28c014
+static GVariant *
28c014
+handle_get_property (GDBusConnection *connection,
28c014
+		     const gchar *sender,
28c014
+		     const gchar *object_path,
28c014
+		     const gchar *interface_name,
28c014
+		     const gchar *property_name,
28c014
+		     GError **error, gpointer user_data)
28c014
+{
28c014
+	GsdSubscriptionManager *manager = GSD_SUBSCRIPTION_MANAGER (user_data);
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+
28c014
+	if (g_strcmp0 (interface_name, GSD_SUBSCRIPTION_DBUS_INTERFACE) != 0) {
28c014
+		g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
28c014
+			     "No such interface: %s", interface_name);
28c014
+		return NULL;
28c014
+	}
28c014
+
28c014
+	if (g_strcmp0 (property_name, "SubscriptionStatus") == 0)
28c014
+		return g_variant_new_uint32 (priv->subscription_status);
28c014
+
28c014
+	g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
28c014
+		     "Failed to get property: %s", property_name);
28c014
+	return NULL;
28c014
+}
28c014
+
28c014
+static gboolean
28c014
+handle_set_property (GDBusConnection *connection,
28c014
+		     const gchar *sender,
28c014
+		     const gchar *object_path,
28c014
+		     const gchar *interface_name,
28c014
+		     const gchar *property_name,
28c014
+		     GVariant *value,
28c014
+		     GError **error, gpointer user_data)
28c014
+{
28c014
+	if (g_strcmp0 (interface_name, GSD_SUBSCRIPTION_DBUS_INTERFACE) != 0) {
28c014
+		g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
28c014
+			     "No such interface: %s", interface_name);
28c014
+		return FALSE;
28c014
+	}
28c014
+	g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
28c014
+		     "No such property: %s", property_name);
28c014
+	return FALSE;
28c014
+}
28c014
+
28c014
+static const GDBusInterfaceVTable interface_vtable =
28c014
+{
28c014
+	handle_method_call,
28c014
+	handle_get_property,
28c014
+	handle_set_property
28c014
+};
28c014
+
28c014
+static void
28c014
+name_lost_handler_cb (GDBusConnection *connection, const gchar *name, gpointer user_data)
28c014
+{
28c014
+	g_debug ("lost name, so exiting");
28c014
+	gtk_main_quit ();
28c014
+}
28c014
+
28c014
+static void
28c014
+on_bus_gotten (GObject *source_object, GAsyncResult *res, GsdSubscriptionManager *manager)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+	GDBusConnection *connection;
28c014
+	g_autoptr(GError) error = NULL;
28c014
+
28c014
+	connection = g_bus_get_finish (res, &error);
28c014
+	if (connection == NULL) {
28c014
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
28c014
+			g_warning ("Could not get session bus: %s", error->message);
28c014
+		return;
28c014
+	}
28c014
+
28c014
+	priv->connection = connection;
28c014
+	g_dbus_connection_register_object (connection,
28c014
+					   GSD_SUBSCRIPTION_DBUS_PATH,
28c014
+					   priv->introspection_data->interfaces[0],
28c014
+					   &interface_vtable,
28c014
+					   manager,
28c014
+					   NULL,
28c014
+					   NULL);
28c014
+	priv->name_id = g_bus_own_name_on_connection (connection,
28c014
+						      GSD_SUBSCRIPTION_DBUS_NAME,
28c014
+						      G_BUS_NAME_OWNER_FLAGS_NONE,
28c014
+						      NULL,
28c014
+						      name_lost_handler_cb,
28c014
+						      manager,
28c014
+						      NULL);
28c014
+}
28c014
+
28c014
+static void
28c014
+register_manager_dbus (GsdSubscriptionManager *manager)
28c014
+{
28c014
+	GsdSubscriptionManagerPrivate *priv = manager->priv;
28c014
+
28c014
+	priv->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
28c014
+	g_assert (priv->introspection_data != NULL);
28c014
+	priv->bus_cancellable = g_cancellable_new ();
28c014
+
28c014
+	g_bus_get (G_BUS_TYPE_SESSION, priv->bus_cancellable,
28c014
+		   (GAsyncReadyCallback) on_bus_gotten, manager);
28c014
+}
28c014
+
28c014
+GsdSubscriptionManager *
28c014
+gsd_subscription_manager_new (void)
28c014
+{
28c014
+	if (manager_object != NULL) {
28c014
+		g_object_ref (manager_object);
28c014
+	} else {
28c014
+		manager_object = g_object_new (GSD_TYPE_SUBSCRIPTION_MANAGER, NULL);
28c014
+		g_object_add_weak_pointer (manager_object,
28c014
+					   (gpointer *) &manager_object);
28c014
+		register_manager_dbus (manager_object);
28c014
+	}
28c014
+
28c014
+	return GSD_SUBSCRIPTION_MANAGER (manager_object);
28c014
+}
e296c6
diff --git a/plugins/subman/gsd-subscription-manager.h b/plugins/subman/gsd-subscription-manager.h
e296c6
new file mode 100644
e296c6
index 00000000..6a524b1b
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/gsd-subscription-manager.h
28c014
@@ -0,0 +1,63 @@
28c014
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
28c014
+ *
28c014
+ * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
28c014
+ *
28c014
+ * This program is free software; you can redistribute it and/or modify
28c014
+ * it under the terms of the GNU General Public License as published by
28c014
+ * the Free Software Foundation; either version 2 of the License, or
28c014
+ * (at your option) any later version.
28c014
+ *
28c014
+ * This program is distributed in the hope that it will be useful,
28c014
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28c014
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28c014
+ * GNU General Public License for more details.
28c014
+ *
28c014
+ * You should have received a copy of the GNU General Public License
28c014
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
28c014
+ *
28c014
+ */
28c014
+
28c014
+#ifndef __GSD_SUBSCRIPTION_MANAGER_H
28c014
+#define __GSD_SUBSCRIPTION_MANAGER_H
28c014
+
28c014
+#include <glib-object.h>
28c014
+
28c014
+G_BEGIN_DECLS
28c014
+
28c014
+#define GSD_TYPE_SUBSCRIPTION_MANAGER		(gsd_subscription_manager_get_type ())
28c014
+#define GSD_SUBSCRIPTION_MANAGER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManager))
28c014
+#define GSD_SUBSCRIPTION_MANAGER_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerClass))
28c014
+#define GSD_IS_SUBSCRIPTION_MANAGER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_SUBSCRIPTION_MANAGER))
28c014
+#define GSD_IS_SUBSCRIPTION_MANAGER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_SUBSCRIPTION_MANAGER))
28c014
+#define GSD_SUBSCRIPTION_MANAGER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerClass))
28c014
+#define GSD_SUBSCRIPTION_MANAGER_ERROR		(gsd_subscription_manager_error_quark ())
28c014
+
28c014
+typedef struct GsdSubscriptionManagerPrivate GsdSubscriptionManagerPrivate;
28c014
+
28c014
+typedef struct
28c014
+{
28c014
+	GObject				 parent;
28c014
+	GsdSubscriptionManagerPrivate	*priv;
28c014
+} GsdSubscriptionManager;
28c014
+
28c014
+typedef struct
28c014
+{
28c014
+	GObjectClass			parent_class;
28c014
+} GsdSubscriptionManagerClass;
28c014
+
28c014
+enum
28c014
+{
28c014
+	GSD_SUBSCRIPTION_MANAGER_ERROR_FAILED
28c014
+};
28c014
+
28c014
+GType			gsd_subscription_manager_get_type       (void);
28c014
+GQuark			gsd_subscription_manager_error_quark    (void);
28c014
+
28c014
+GsdSubscriptionManager *gsd_subscription_manager_new		(void);
28c014
+gboolean		gsd_subscription_manager_start		(GsdSubscriptionManager *manager,
28c014
+								 GError                **error);
28c014
+void			gsd_subscription_manager_stop		(GsdSubscriptionManager *manager);
28c014
+
28c014
+G_END_DECLS
28c014
+
28c014
+#endif /* __GSD_SUBSCRIPTION_MANAGER_H */
e296c6
diff --git a/plugins/subman/main.c b/plugins/subman/main.c
e296c6
new file mode 100644
e296c6
index 00000000..28ac995b
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/main.c
28c014
@@ -0,0 +1,8 @@
28c014
+#define NEW gsd_subscription_manager_new
28c014
+#define START gsd_subscription_manager_start
28c014
+#define STOP gsd_subscription_manager_stop
28c014
+#define MANAGER GsdSubscriptionManager
28c014
+#define GDK_BACKEND "x11"
28c014
+#include "gsd-subscription-manager.h"
28c014
+
28c014
+#include "daemon-skeleton-gtk.h"
e296c6
diff --git a/plugins/subman/meson.build b/plugins/subman/meson.build
e296c6
new file mode 100644
e296c6
index 00000000..bfd073b6
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/meson.build
28c014
@@ -0,0 +1,56 @@
28c014
+sources = files(
28c014
+  'gsd-subscription-manager.c',
28c014
+  'gsd-subman-common.c',
28c014
+  'main.c'
28c014
+)
28c014
+
28c014
+deps = plugins_deps + [
28c014
+  libnotify_dep,
28c014
+  gtk_dep,
28c014
+  jsonglib_dep,
28c014
+  m_dep,
28c014
+]
28c014
+
28c014
+cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)]
28c014
+cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
28c014
+
28c014
+executable(
28c014
+  'gsd-' + plugin_name,
28c014
+  sources,
28c014
+  include_directories: [top_inc, common_inc],
28c014
+  dependencies: deps,
28c014
+  c_args: cflags,
28c014
+  install: true,
28c014
+  install_rpath: gsd_pkglibdir,
28c014
+  install_dir: gsd_libexecdir
28c014
+)
28c014
+
28c014
+# .Register needs to be called from root as subman can't do PolicyKit...
28c014
+policy = 'org.gnome.settings-daemon.plugins.subman.policy'
28c014
+policy_in = configure_file(
28c014
+  input: policy + '.in.in',
28c014
+  output: policy + '.in',
28c014
+  configuration: plugins_conf
28c014
+)
28c014
+
28c014
+i18n.merge_file(
28c014
+  policy,
28c014
+  input: policy_in,
28c014
+  output: policy,
28c014
+  po_dir: po_dir,
28c014
+  install: true,
28c014
+  install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions')
28c014
+)
28c014
+
28c014
+install_data('org.gnome.settings-daemon.plugins.subman.rules',
28c014
+             install_dir : join_paths(gsd_datadir, 'polkit-1', 'rules.d'))
28c014
+
28c014
+executable(
28c014
+  'gsd-subman-helper',
28c014
+  'gsd-subman-helper.c',
28c014
+  include_directories: top_inc,
28c014
+  dependencies: [gio_dep, jsonglib_dep],
28c014
+  install: true,
28c014
+  install_rpath: gsd_pkglibdir,
28c014
+  install_dir: gsd_libexecdir
28c014
+)
e296c6
diff --git a/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in b/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
e296c6
new file mode 100644
e296c6
index 00000000..14fe5915
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
e296c6
@@ -0,0 +1,9 @@
e296c6
+[Desktop Entry]
e296c6
+Type=Application
e296c6
+Name=GNOME Settings Daemon's subscription manager plugin
e296c6
+Exec=@libexecdir@/gsd-subman
e296c6
+OnlyShowIn=GNOME;
e296c6
+NoDisplay=true
e296c6
+X-GNOME-Autostart-Phase=Initialization
e296c6
+X-GNOME-Autostart-Notify=true
e296c6
+X-GNOME-AutoRestart=true
e296c6
diff --git a/plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in b/plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
e296c6
new file mode 100644
e296c6
index 00000000..59e9fdd4
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
e296c6
@@ -0,0 +1,27 @@
28c014
+
28c014
+
28c014
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
28c014
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
28c014
+<policyconfig>
28c014
+
28c014
+  
28c014
+    Policy definitions for gnome-settings-daemon system-wide actions.
28c014
+    Copyright (c) 2019 Richard Hughes <richard@hughsie.com>
28c014
+  -->
28c014
+
28c014
+  <vendor>GNOME Settings Daemon</vendor>
28c014
+  <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url>
28c014
+  <icon_name>emblem-synchronizing</icon_name>
28c014
+
28c014
+  <action id="org.gnome.settings-daemon.plugins.subman.register">
28c014
+    <description>Register the system</description>
28c014
+    <message>Authentication is required to register the system</message>
28c014
+    <defaults>
28c014
+      <allow_any>no</allow_any>
28c014
+      <allow_inactive>no</allow_inactive>
28c014
+      <allow_active>auth_admin_keep</allow_active>
28c014
+    </defaults>
28c014
+    <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-subman-helper</annotate>
28c014
+  </action>
28c014
+
28c014
+</policyconfig>
e296c6
diff --git a/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules b/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
e296c6
new file mode 100644
e296c6
index 00000000..1ed3a0ea
e296c6
--- /dev/null
e296c6
+++ b/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
28c014
@@ -0,0 +1,7 @@
28c014
+polkit.addRule(function(action, subject) {
28c014
+    if (action.id == "org.gnome.settings-daemon.plugins.subman.register" &&
28c014
+        subject.active == true && subject.local == true &&
28c014
+        subject.isInGroup("wheel")) {
28c014
+            return polkit.Result.YES;
28c014
+    }
28c014
+});
1ff027
-- 
e296c6
2.30.0
1ff027