summaryrefslogtreecommitdiff
path: root/subscription-manager-support.patch
diff options
context:
space:
mode:
Diffstat (limited to 'subscription-manager-support.patch')
-rw-r--r--subscription-manager-support.patch3804
1 files changed, 3804 insertions, 0 deletions
diff --git a/subscription-manager-support.patch b/subscription-manager-support.patch
new file mode 100644
index 0000000..e335163
--- /dev/null
+++ b/subscription-manager-support.patch
@@ -0,0 +1,3804 @@
+From 31123047c5e198f00c2e7ee35fe77d508100659a Mon Sep 17 00:00:00 2001
+From: Richard Hughes <rhughes@redhat.com>
+Date: Thu, 20 Aug 2020 11:16:09 -0400
+Subject: [PATCH 01/21] subman: Add a new plugin to provide system subscription
+ registration
+
+---
+ meson.build | 1 +
+ plugins/meson.build | 1 +
+ plugins/subman/README.md | 56 +
+ plugins/subman/gsd-subman-common.c | 36 +
+ plugins/subman/gsd-subman-common.h | 40 +
+ plugins/subman/gsd-subman-helper.c | 371 +++++++
+ plugins/subman/gsd-subscription-manager.c | 982 ++++++++++++++++++
+ plugins/subman/gsd-subscription-manager.h | 63 ++
+ plugins/subman/main.c | 8 +
+ plugins/subman/meson.build | 56 +
+ ...ome.SettingsDaemon.Subscription.desktop.in | 9 +
+ ...ettings-daemon.plugins.subman.policy.in.in | 27 +
+ ...gnome.settings-daemon.plugins.subman.rules | 7 +
+ 13 files changed, 1657 insertions(+)
+ create mode 100644 plugins/subman/README.md
+ create mode 100644 plugins/subman/gsd-subman-common.c
+ create mode 100644 plugins/subman/gsd-subman-common.h
+ create mode 100644 plugins/subman/gsd-subman-helper.c
+ create mode 100644 plugins/subman/gsd-subscription-manager.c
+ create mode 100644 plugins/subman/gsd-subscription-manager.h
+ create mode 100644 plugins/subman/main.c
+ create mode 100644 plugins/subman/meson.build
+ create mode 100644 plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
+ create mode 100644 plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
+ create mode 100644 plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
+
+diff --git a/meson.build b/meson.build
+index 88e4f87d..644ba060 100644
+--- a/meson.build
++++ b/meson.build
+@@ -104,6 +104,7 @@ libcanberra_gtk_dep = dependency('libcanberra-gtk3')
+ libgeoclue_dep = dependency('libgeoclue-2.0', version: '>= 2.3.1')
+ libnotify_dep = dependency('libnotify', version: '>= 0.7.3')
+ libpulse_mainloop_glib_dep = dependency('libpulse-mainloop-glib', version: '>= 2.0')
++jsonglib_dep = dependency('json-glib-1.0', version: '>= 1.1.1')
+ pango_dep = dependency('pango', version: '>= 1.20.0')
+ polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.114')
+ upower_glib_dep = dependency('upower-glib', version: '>= 0.99.8')
+diff --git a/plugins/meson.build b/plugins/meson.build
+index 5d5c6e9b..59266c8d 100644
+--- a/plugins/meson.build
++++ b/plugins/meson.build
+@@ -1,6 +1,7 @@
+ all_plugins = [
+ ['a11y-settings', 'A11ySettings', 'GNOME accessibility'],
+ ['color', 'Color', 'GNOME color management'],
++ ['subman', 'Subscription', 'GNOME subscription management'],
+ ['datetime', 'Datetime', 'GNOME date & time'],
+ ['power', 'Power', 'GNOME power management'],
+ ['housekeeping', 'Housekeeping', 'GNOME maintenance of expirable data'],
+diff --git a/plugins/subman/README.md b/plugins/subman/README.md
+new file mode 100644
+index 00000000..3e1cc3cd
+--- /dev/null
++++ b/plugins/subman/README.md
+@@ -0,0 +1,56 @@
++GNOME Settings Daemon: Subscription Manager Plugin
++==================================================
++
++Testing:
++
++To add a test acccount on subscription.rhsm.stage.redhat.com, use Ethel:
++http://account-manager-stage.app.eng.rdu2.redhat.com/#view
++
++Register with a username and password
++-------------------------------------
++
++ gdbus call \
++ --session \
++ --dest org.gnome.SettingsDaemon.Subscription \
++ --object-path /org/gnome/SettingsDaemon/Subscription \
++ --method org.gnome.SettingsDaemon.Subscription.Register "{'kind':<'username'>,'hostname':<'subscription.rhsm.stage.redhat.com'>,'username':<'rhughes_test'>,'password':<'barbaz'>}"
++
++To register with a certificate
++------------------------------
++
++ gdbus call \
++ --session \
++ --dest org.gnome.SettingsDaemon.Subscription \
++ --object-path /org/gnome/SettingsDaemon/Subscription \
++ --method org.gnome.SettingsDaemon.Subscription.Register "{'kind':<'key'>,'hostname':<'subscription.rhsm.stage.redhat.com'>,'organisation':<'foo'>,'activation-key':<'barbaz'>}"
++
++To unregister
++-------------
++
++ gdbus call \
++ --session \
++ --dest org.gnome.SettingsDaemon.Subscription \
++ --object-path /org/gnome/SettingsDaemon/Subscription \
++ --method org.gnome.SettingsDaemon.Subscription.Unregister
++
++Debugging
++---------
++
++Get the UNIX socket using `Subscription.Register` then call something like:
++
++ sudo G_MESSAGES_DEBUG=all ./plugins/subman/gsd-subman-helper \
++ --address="unix:abstract=/var/run/dbus-ulGB1wfnbn,guid=71e6bf329d861ce366df7a1d5d036a5b" \
++ --kind="register-with-username" \
++ --username="rhughes_test" \
++ --password="barbaz" \
++ --hostname="subscription.rhsm.stage.redhat.com" \
++ --organisation=""
++
++You can all see some basic debugging running `rhsmd` in the foreground:
++
++ sudo /usr/libexec/rhsmd -d -k
++
++Known Limitations
++=================
++
++Proxy servers are not supported, nor are custom host ports or prefixes.
+diff --git a/plugins/subman/gsd-subman-common.c b/plugins/subman/gsd-subman-common.c
+new file mode 100644
+index 00000000..e515131e
+--- /dev/null
++++ b/plugins/subman/gsd-subman-common.c
+@@ -0,0 +1,36 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
++ *
++ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
++ *
++ * 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, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#include "config.h"
++
++#include "gsd-subman-common.h"
++
++const gchar *
++gsd_subman_subscription_status_to_string (GsdSubmanSubscriptionStatus status)
++{
++ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID)
++ return "valid";
++ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID)
++ return "invalid";
++ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED)
++ return "disabled";
++ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID)
++ return "partially-valid";
++ return "unknown";
++}
+diff --git a/plugins/subman/gsd-subman-common.h b/plugins/subman/gsd-subman-common.h
+new file mode 100644
+index 00000000..fccf9f6a
+--- /dev/null
++++ b/plugins/subman/gsd-subman-common.h
+@@ -0,0 +1,40 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
++ *
++ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
++ *
++ * 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, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#ifndef __GSD_SUBMAN_COMMON_H
++#define __GSD_SUBMAN_COMMON_H
++
++#include <glib-object.h>
++
++G_BEGIN_DECLS
++
++typedef enum {
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_LAST
++} GsdSubmanSubscriptionStatus;
++
++const gchar *gsd_subman_subscription_status_to_string (GsdSubmanSubscriptionStatus status);
++
++G_END_DECLS
++
++#endif /* __GSD_SUBMAN_COMMON_H */
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+new file mode 100644
+index 00000000..b1ea48a3
+--- /dev/null
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -0,0 +1,371 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
++ *
++ * Copyright (C) 2019 Richard Hughes <rhughes@redhat.com>
++ *
++ * Licensed under the GNU General Public License Version 2
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++ */
++
++#include "config.h"
++
++#include <sys/types.h>
++#include <unistd.h>
++#include <stdlib.h>
++
++#include <gio/gio.h>
++#include <json-glib/json-glib.h>
++
++static void
++_helper_convert_error (const gchar *json_txt, GError **error)
++{
++ JsonNode *json_root;
++ JsonObject *json_obj;
++ const gchar *message;
++ g_autoptr(JsonParser) json_parser = json_parser_new ();
++
++ /* this may be plain text or JSON :| */
++ if (!json_parser_load_from_data (json_parser, json_txt, -1, NULL)) {
++ g_set_error_literal (error,
++ G_IO_ERROR,
++ G_IO_ERROR_NOT_SUPPORTED,
++ json_txt);
++ return;
++ }
++ json_root = json_parser_get_root (json_parser);
++ json_obj = json_node_get_object (json_root);
++ if (!json_object_has_member (json_obj, "message")) {
++ g_set_error (error,
++ G_IO_ERROR,
++ G_IO_ERROR_INVALID_DATA,
++ "no message' in %s", json_txt);
++ return;
++ }
++ message = json_object_get_string_member (json_obj, "message");
++ if (g_strstr_len (message, -1, "Invalid user credentials") != NULL) {
++ g_set_error_literal (error,
++ G_IO_ERROR,
++ G_IO_ERROR_PERMISSION_DENIED,
++ message);
++ return;
++ }
++ g_set_error_literal (error,
++ G_IO_ERROR,
++ G_IO_ERROR_NOT_SUPPORTED,
++ message);
++}
++
++static gboolean
++_helper_unregister (GError **error)
++{
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GVariantBuilder) proxy_options = NULL;
++ g_autoptr(GVariant) res = NULL;
++
++ g_debug ("unregistering");
++ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
++ NULL,
++ "com.redhat.RHSM1",
++ "/com/redhat/RHSM1/Unregister",
++ "com.redhat.RHSM1.Unregister",
++ NULL, error);
++ if (proxy == NULL) {
++ g_prefix_error (error, "Failed to get proxy: ");
++ return FALSE;
++ }
++ proxy_options = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
++ res = g_dbus_proxy_call_sync (proxy,
++ "Unregister",
++ g_variant_new ("(a{sv}s)",
++ proxy_options,
++ ""), /* lang */
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ return res != NULL;
++}
++
++static gboolean
++_helper_auto_attach (GError **error)
++{
++ const gchar *str = NULL;
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GVariantBuilder) proxy_options = NULL;
++ g_autoptr(GVariant) res = NULL;
++
++ g_debug ("auto-attaching subscriptions");
++ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
++ NULL,
++ "com.redhat.RHSM1",
++ "/com/redhat/RHSM1/Attach",
++ "com.redhat.RHSM1.Attach",
++ NULL, error);
++ if (proxy == NULL) {
++ g_prefix_error (error, "Failed to get proxy: ");
++ return FALSE;
++ }
++ proxy_options = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
++ res = g_dbus_proxy_call_sync (proxy,
++ "AutoAttach",
++ g_variant_new ("(sa{sv}s)",
++ "", /* now? */
++ proxy_options,
++ ""), /* lang */
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (res == NULL)
++ return FALSE;
++ g_variant_get (res, "(&s)", &str);
++ g_debug ("Attach.AutoAttach: %s", str);
++ return TRUE;
++}
++
++static gboolean
++_helper_save_config (const gchar *key, const gchar *value, GError **error)
++{
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GVariant) res = NULL;
++ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
++ NULL,
++ "com.redhat.RHSM1",
++ "/com/redhat/RHSM1/Config",
++ "com.redhat.RHSM1.Config",
++ NULL, error);
++ if (proxy == NULL) {
++ g_prefix_error (error, "Failed to get proxy: ");
++ return FALSE;
++ }
++ res = g_dbus_proxy_call_sync (proxy, "Set",
++ g_variant_new ("(svs)",
++ key,
++ g_variant_new_string (value),
++ ""), /* lang */
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ return res != NULL;
++}
++
++int
++main (int argc, char *argv[])
++{
++ const gchar *userlang = ""; /* as root, so no translations */
++ g_autofree gchar *activation_key = NULL;
++ g_autofree gchar *address = NULL;
++ g_autofree gchar *hostname = NULL;
++ g_autofree gchar *kind = NULL;
++ g_autofree gchar *organisation = NULL;
++ g_autofree gchar *password = NULL;
++ g_autofree gchar *port = NULL;
++ g_autofree gchar *prefix = NULL;
++ g_autofree gchar *proxy_server = NULL;
++ g_autofree gchar *username = NULL;
++ g_autoptr(GDBusConnection) conn_private = NULL;
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GError) error = NULL;
++ g_autoptr(GOptionContext) context = g_option_context_new (NULL);
++ g_autoptr(GVariantBuilder) proxy_options = NULL;
++ g_autoptr(GVariantBuilder) subman_conopts = NULL;
++ g_autoptr(GVariantBuilder) subman_options = NULL;
++
++ const GOptionEntry options[] = {
++ { "kind", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &kind, "Kind, e.g. 'username' or 'key'", NULL },
++ { "address", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &address, "UNIX address", NULL },
++ { "username", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &username, "Username", NULL },
++ { "password", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &password, "Password", NULL },
++ { "organisation", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &organisation, "Organisation", NULL },
++ { "activation-key", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
++ &activation_key, "Activation keys", NULL },
++ { "hostname", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
++ &hostname, "Registration server hostname", NULL },
++ { "prefix", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
++ &prefix, "Registration server prefix", NULL },
++ { "port", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
++ &port, "Registration server port", NULL },
++ { "proxy", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
++ &proxy_server, "Proxy settings", NULL },
++ { NULL}
++ };
++
++ /* check calling UID */
++ if (getuid () != 0 || geteuid () != 0) {
++ g_printerr ("This program can only be used by the root user\n");
++ return G_IO_ERROR_NOT_SUPPORTED;
++ }
++ g_option_context_add_main_entries (context, options, NULL);
++ if (!g_option_context_parse (context, &argc, &argv, &error)) {
++ g_printerr ("Failed to parse arguments: %s\n", error->message);
++ return G_IO_ERROR_NOT_SUPPORTED;
++ }
++
++ /* uncommon actions */
++ if (kind == NULL) {
++ g_printerr ("No --kind specified\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ if (g_strcmp0 (kind, "unregister") == 0) {
++ if (!_helper_unregister (&error)) {
++ g_printerr ("Failed to Unregister: %s\n", error->message);
++ return G_IO_ERROR_NOT_INITIALIZED;
++ }
++ return EXIT_SUCCESS;
++ }
++ if (g_strcmp0 (kind, "auto-attach") == 0) {
++ if (!_helper_auto_attach (&error)) {
++ g_printerr ("Failed to AutoAttach: %s\n", error->message);
++ return G_IO_ERROR_NOT_INITIALIZED;
++ }
++ return EXIT_SUCCESS;
++ }
++
++ /* connect to abstract socket for reasons */
++ if (address == NULL) {
++ g_printerr ("No --address specified\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ conn_private = g_dbus_connection_new_for_address_sync (address,
++ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
++ NULL, NULL,
++ &error);
++ if (conn_private == NULL) {
++ g_printerr ("Invalid --address specified: %s\n", error->message);
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ proxy = g_dbus_proxy_new_sync (conn_private,
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
++ NULL, /* GDBusInterfaceInfo */
++ NULL, /* name */
++ "/com/redhat/RHSM1/Register",
++ "com.redhat.RHSM1.Register",
++ NULL, &error);
++ if (proxy == NULL) {
++ g_printerr ("Count not contact RHSM: %s\n", error->message);
++ return G_IO_ERROR_NOT_FOUND;
++ }
++
++ /* enable_content=1 auto attaches the subscription */
++ subman_options = g_variant_builder_new (G_VARIANT_TYPE("a{ss}"));
++
++ g_variant_builder_add (subman_options, "{ss}", "enable_content", "1");
++
++ /* set registration server */
++ if (hostname == NULL || hostname[0] == '\0')
++ hostname = g_strdup ("subscription.rhsm.redhat.com");
++ if (prefix == NULL || prefix[0] == '\0')
++ prefix = g_strdup ("/subscription");
++ if (port == NULL || port[0] == '\0')
++ port = g_strdup ("443");
++ subman_conopts = g_variant_builder_new (G_VARIANT_TYPE("a{ss}"));
++ g_variant_builder_add (subman_conopts, "{ss}", "host", hostname);
++ g_variant_builder_add (subman_conopts, "{ss}", "handler", prefix);
++ g_variant_builder_add (subman_conopts, "{ss}", "port", port);
++
++ /* call into RHSM */
++ if (g_strcmp0 (kind, "register-with-key") == 0) {
++ g_auto(GStrv) activation_keys = NULL;
++ g_autoptr(GError) error_local = NULL;
++ g_autoptr(GVariant) res = NULL;
++
++ if (activation_key == NULL) {
++ g_printerr ("Required --activation-key\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ if (organisation == NULL) {
++ g_printerr ("Required --organisation\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++
++ g_debug ("registering using activation key");
++ activation_keys = g_strsplit (activation_key, ",", -1);
++ res = g_dbus_proxy_call_sync (proxy,
++ "RegisterWithActivationKeys",
++ g_variant_new ("(s^asa{ss}a{ss}s)",
++ organisation,
++ activation_keys,
++ subman_options,
++ subman_conopts,
++ userlang),
++ G_DBUS_CALL_FLAGS_NO_AUTO_START,
++ -1, NULL, &error_local);
++ if (res == NULL) {
++ g_dbus_error_strip_remote_error (error_local);
++ _helper_convert_error (error_local->message, &error);
++ g_printerr ("Failed to RegisterWithActivationKeys: %s\n", error->message);
++ return error->code;
++ }
++ } else if (g_strcmp0 (kind, "register-with-username") == 0) {
++ g_autoptr(GError) error_local = NULL;
++ g_autoptr(GVariant) res = NULL;
++
++ g_debug ("registering using username and password");
++ if (username == NULL) {
++ g_printerr ("Required --username\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ if (password == NULL) {
++ g_printerr ("Required --password\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ if (organisation == NULL) {
++ g_printerr ("Required --organisation\n");
++ return G_IO_ERROR_INVALID_DATA;
++ }
++ res = g_dbus_proxy_call_sync (proxy,
++ "Register",
++ g_variant_new ("(sssa{ss}a{ss}s)",
++ organisation,
++ username,
++ password,
++ subman_options,
++ subman_conopts,
++ userlang),
++ G_DBUS_CALL_FLAGS_NO_AUTO_START,
++ -1, NULL, &error_local);
++ if (res == NULL) {
++ g_dbus_error_strip_remote_error (error_local);
++ _helper_convert_error (error_local->message, &error);
++ g_printerr ("Failed to Register: %s\n", error->message);
++ return error->code;
++ }
++ } else {
++ g_printerr ("Invalid --kind specified: %s\n", kind);
++ return G_IO_ERROR_INVALID_DATA;
++ }
++
++ /* set the new hostname */
++ if (!_helper_save_config ("server.hostname", hostname, &error)) {
++ g_printerr ("Failed to save hostname: %s\n", error->message);
++ return G_IO_ERROR_NOT_INITIALIZED;
++ }
++ if (!_helper_save_config ("server.prefix", prefix, &error)) {
++ g_printerr ("Failed to save prefix: %s\n", error->message);
++ return G_IO_ERROR_NOT_INITIALIZED;
++ }
++ if (!_helper_save_config ("server.port", port, &error)) {
++ g_printerr ("Failed to save port: %s\n", error->message);
++ return G_IO_ERROR_NOT_INITIALIZED;
++ }
++
++ return EXIT_SUCCESS;
++}
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+new file mode 100644
+index 00000000..08b13fa6
+--- /dev/null
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -0,0 +1,982 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
++ *
++ * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
++ *
++ * 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, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#include "config.h"
++
++#include <glib/gi18n.h>
++#include <gdk/gdk.h>
++#include <gtk/gtk.h>
++#include <json-glib/json-glib.h>
++#include <libnotify/notify.h>
++
++#include "gnome-settings-profile.h"
++#include "gsd-subman-common.h"
++#include "gsd-subscription-manager.h"
++
++#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
++#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
++#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
++
++#define GSD_SUBSCRIPTION_DBUS_NAME GSD_DBUS_NAME ".Subscription"
++#define GSD_SUBSCRIPTION_DBUS_PATH GSD_DBUS_PATH "/Subscription"
++#define GSD_SUBSCRIPTION_DBUS_INTERFACE GSD_DBUS_BASE_INTERFACE ".Subscription"
++
++static const gchar introspection_xml[] =
++"<node>"
++" <interface name='org.gnome.SettingsDaemon.Subscription'>"
++" <method name='Register'>"
++" <arg type='a{sv}' name='options' direction='in'/>"
++" </method>"
++" <method name='Unregister'/>"
++" <property name='SubscriptionStatus' type='u' access='read'/>"
++" </interface>"
++"</node>";
++
++#define GSD_SUBSCRIPTION_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerPrivate))
++
++typedef enum {
++ _RHSM_INTERFACE_CONFIG,
++ _RHSM_INTERFACE_REGISTER_SERVER,
++ _RHSM_INTERFACE_ATTACH,
++ _RHSM_INTERFACE_ENTITLEMENT,
++ _RHSM_INTERFACE_PRODUCTS,
++ _RHSM_INTERFACE_CONSUMER,
++ _RHSM_INTERFACE_SYSPURPOSE,
++ _RHSM_INTERFACE_LAST
++} _RhsmInterface;
++
++struct GsdSubscriptionManagerPrivate
++{
++ /* D-Bus */
++ guint name_id;
++ GDBusNodeInfo *introspection_data;
++ GDBusConnection *connection;
++ GCancellable *bus_cancellable;
++
++ GDBusProxy *proxies[_RHSM_INTERFACE_LAST];
++ const gchar *userlang; /* owned by GLib internally */
++ GHashTable *config; /* str:str */
++ gchar *address;
++
++ GTimer *timer_last_notified;
++ NotifyNotification *notification_expired;
++ NotifyNotification *notification_registered;
++ NotifyNotification *notification_registration_required;
++ GsdSubmanSubscriptionStatus subscription_status;
++ GsdSubmanSubscriptionStatus subscription_status_last;
++};
++
++enum {
++ PROP_0,
++};
++
++static void gsd_subscription_manager_class_init (GsdSubscriptionManagerClass *klass);
++static void gsd_subscription_manager_init (GsdSubscriptionManager *subscription_manager);
++static void gsd_subscription_manager_finalize (GObject *object);
++
++G_DEFINE_TYPE (GsdSubscriptionManager, gsd_subscription_manager, G_TYPE_OBJECT)
++
++static gpointer manager_object = NULL;
++
++GQuark
++gsd_subscription_manager_error_quark (void)
++{
++ static GQuark quark = 0;
++ if (!quark)
++ quark = g_quark_from_static_string ("gsd_subscription_manager_error");
++ return quark;
++}
++
++static GsdSubmanSubscriptionStatus
++_client_subscription_status_from_text (const gchar *status_txt)
++{
++ if (g_strcmp0 (status_txt, "Unknown") == 0)
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
++ if (g_strcmp0 (status_txt, "Current") == 0)
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID;
++ if (g_strcmp0 (status_txt, "Invalid") == 0)
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
++ if (g_strcmp0 (status_txt, "Disabled") == 0)
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED;
++ if (g_strcmp0 (status_txt, "Insufficient") == 0)
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID;
++ g_warning ("Unknown subscription status: %s", status_txt); // 'Current'?
++ return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
++}
++
++static void
++_emit_property_changed (GsdSubscriptionManager *manager,
++ const gchar *property_name,
++ GVariant *property_value)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ GVariantBuilder builder;
++ GVariantBuilder invalidated_builder;
++
++ /* not yet connected */
++ if (priv->connection == NULL)
++ return;
++
++ /* build the dict */
++ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
++ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
++ g_variant_builder_add (&builder,
++ "{sv}",
++ property_name,
++ property_value);
++ g_dbus_connection_emit_signal (priv->connection,
++ NULL,
++ GSD_SUBSCRIPTION_DBUS_PATH,
++ "org.freedesktop.DBus.Properties",
++ "PropertiesChanged",
++ g_variant_new ("(sa{sv}as)",
++ GSD_SUBSCRIPTION_DBUS_INTERFACE,
++ &builder,
++ &invalidated_builder),
++ NULL);
++ g_variant_builder_clear (&builder);
++ g_variant_builder_clear (&invalidated_builder);
++}
++
++static gboolean
++_client_subscription_status_update (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ JsonNode *json_root;
++ JsonObject *json_obj;
++ const gchar *json_txt = NULL;
++ const gchar *status_txt = NULL;
++ g_autoptr(GVariant) val = NULL;
++ g_autoptr(JsonParser) json_parser = json_parser_new ();
++
++ /* save old value */
++ priv->subscription_status_last = priv->subscription_status;
++
++ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_ENTITLEMENT],
++ "GetStatus",
++ g_variant_new ("(ss)",
++ "", /* assumed as 'now' */
++ priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ g_variant_get (val, "(&s)", &json_txt);
++ g_debug ("Entitlement.GetStatus JSON: %s", json_txt);
++ if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
++ return FALSE;
++ json_root = json_parser_get_root (json_parser);
++ json_obj = json_node_get_object (json_root);
++ if (!json_object_has_member (json_obj, "status")) {
++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
++ "no Entitlement.GetStatus status in %s", json_txt);
++ return FALSE;
++ }
++
++ status_txt = json_object_get_string_member (json_obj, "status");
++ g_debug ("Entitlement.GetStatus: %s", status_txt);
++ priv->subscription_status = _client_subscription_status_from_text (status_txt);
++
++ /* emit notification for g-c-c */
++ if (priv->subscription_status != priv->subscription_status_last) {
++ _emit_property_changed (manager, "SubscriptionStatus",
++ g_variant_new_uint32 (priv->subscription_status));
++ }
++
++ return TRUE;
++}
++
++static gboolean
++_client_syspurpose_update (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ JsonNode *json_root;
++ JsonObject *json_obj;
++ const gchar *json_txt = NULL;
++ g_autoptr(GVariant) val = NULL;
++ g_autoptr(JsonParser) json_parser = json_parser_new ();
++
++ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_SYSPURPOSE],
++ "GetSyspurpose",
++ g_variant_new ("(s)", priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ g_variant_get (val, "(&s)", &json_txt);
++ g_debug ("Syspurpose.GetSyspurpose JSON: %s", json_txt);
++ if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
++ return FALSE;
++ json_root = json_parser_get_root (json_parser);
++ json_obj = json_node_get_object (json_root);
++ if (!json_object_has_member (json_obj, "status")) {
++ g_debug ("Syspurpose.GetSyspurpose: Unknown");
++ return TRUE;
++ }
++ g_debug ("Syspurpose.GetSyspurpose: '%s", json_object_get_string_member (json_obj, "status"));
++ return TRUE;
++}
++
++static gboolean
++_client_register_start (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ const gchar *address = NULL;
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GVariant) val = NULL;
++
++ /* already started */
++ if (priv->address != NULL)
++ return TRUE;
++
++ /* apparently: "we can't send registration credentials over the regular
++ * system or session bus since those aren't really locked down..." */
++ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_NONE,
++ NULL,
++ "com.redhat.RHSM1",
++ "/com/redhat/RHSM1/RegisterServer",
++ "com.redhat.RHSM1.RegisterServer",
++ NULL, error);
++ if (proxy == NULL)
++ return FALSE;
++ val = g_dbus_proxy_call_sync (proxy, "Start",
++ g_variant_new ("(s)", priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ g_variant_get (val, "(&s)", &address);
++ g_debug ("RegisterServer.Start: %s", address);
++ priv->address = g_strdup (address);
++ return TRUE;
++}
++
++static gboolean
++_client_register_stop (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GDBusProxy) proxy = NULL;
++ g_autoptr(GVariant) val = NULL;
++
++ /* already started */
++ if (priv->address == NULL)
++ return TRUE;
++
++ /* stop registration server */
++ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_NONE,
++ NULL,
++ "com.redhat.RHSM1",
++ "/com/redhat/RHSM1/RegisterServer",
++ "com.redhat.RHSM1.RegisterServer",
++ NULL, error);
++ if (proxy == NULL)
++ return FALSE;
++ val = g_dbus_proxy_call_sync (proxy, "Stop",
++ g_variant_new ("(s)", priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ g_clear_pointer (&priv->address, g_free);
++ return TRUE;
++}
++
++static gboolean
++_client_subprocess_wait_check (GSubprocess *subprocess, GError **error)
++{
++ gint rc;
++ if (!g_subprocess_wait (subprocess, NULL, error)) {
++ g_prefix_error (error, "failed to run pkexec: ");
++ return FALSE;
++ }
++ rc = g_subprocess_get_exit_status (subprocess);
++ if (rc != 0) {
++ GInputStream *istream = g_subprocess_get_stderr_pipe (subprocess);
++ gchar buf[1024] = { 0x0 };
++ gsize sz = 0;
++ g_input_stream_read_all (istream, buf, sizeof(buf) - 1, &sz, NULL, NULL);
++ if (sz == 0) {
++ g_set_error_literal (error, G_IO_ERROR, rc,
++ "Failed to run helper without stderr");
++ return FALSE;
++ }
++ g_set_error_literal (error, G_IO_ERROR, rc, buf);
++ return FALSE;
++ }
++ return TRUE;
++}
++
++typedef enum {
++ _NOTIFY_EXPIRED,
++ _NOTIFY_REGISTRATION_REQUIRED,
++ _NOTIFY_REGISTERED
++} _NotifyKind;
++
++static void
++_show_notification (GsdSubscriptionManager *manager, _NotifyKind notify_kind)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ switch (notify_kind) {
++ case _NOTIFY_EXPIRED:
++ notify_notification_close (priv->notification_registered, NULL);
++ notify_notification_close (priv->notification_registration_required, NULL);
++ notify_notification_show (priv->notification_expired, NULL);
++ break;
++ case _NOTIFY_REGISTRATION_REQUIRED:
++ notify_notification_close (priv->notification_registered, NULL);
++ notify_notification_close (priv->notification_expired, NULL);
++ notify_notification_show (priv->notification_registration_required, NULL);
++ break;
++ case _NOTIFY_REGISTERED:
++ notify_notification_close (priv->notification_expired, NULL);
++ notify_notification_close (priv->notification_registration_required, NULL);
++ notify_notification_show (priv->notification_registered, NULL);
++ break;
++ default:
++ break;
++ }
++ g_timer_reset (priv->timer_last_notified);
++}
++
++static void
++_client_maybe__show_notification (GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++
++ /* startup */
++ if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
++ priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
++ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
++ return;
++ }
++
++ /* something changed */
++ if (priv->subscription_status_last != priv->subscription_status) {
++ g_debug ("transisition from subscription status '%s' to '%s'",
++ gsd_subman_subscription_status_to_string (priv->subscription_status_last),
++ gsd_subman_subscription_status_to_string (priv->subscription_status));
++
++ /* needs registration */
++ if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
++ priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID) {
++ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
++ return;
++ }
++
++ /* was unregistered */
++ if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
++ priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
++ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
++ return;
++ }
++
++ /* registered */
++ if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
++ priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
++ g_timer_elapsed (priv->timer_last_notified, NULL) > 60) {
++ _show_notification (manager, _NOTIFY_REGISTERED);
++ return;
++ }
++ }
++
++ /* nag again */
++ if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
++ g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
++ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
++ return;
++ }
++ if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID &&
++ g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
++ _show_notification (manager, _NOTIFY_EXPIRED);
++ return;
++ }
++ if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID &&
++ g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
++ _show_notification (manager, _NOTIFY_EXPIRED);
++ return;
++ }
++}
++
++static gboolean
++_client_register_with_keys (GsdSubscriptionManager *manager,
++ const gchar *hostname,
++ const gchar *organisation,
++ const gchar *activation_key,
++ GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GSubprocess) subprocess = NULL;
++
++ /* apparently: "we can't send registration credentials over the regular
++ * system or session bus since those aren't really locked down..." */
++ if (!_client_register_start (manager, error))
++ return FALSE;
++ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
++ "pkexec", LIBEXECDIR "/gsd-subman-helper",
++ "--kind", "register-with-key",
++ "--address", priv->address,
++ "--hostname", hostname,
++ "--organisation", organisation,
++ "--activation-key", activation_key,
++ NULL);
++ if (subprocess == NULL) {
++ g_prefix_error (error, "failed to find pkexec: ");
++ return FALSE;
++ }
++ if (!_client_subprocess_wait_check (subprocess, error))
++ return FALSE;
++
++ /* FIXME: also do on error? */
++ if (!_client_register_stop (manager, error))
++ return FALSE;
++ if (!_client_subscription_status_update (manager, error))
++ return FALSE;
++ _client_maybe__show_notification (manager);
++
++ /* success */
++ return TRUE;
++}
++
++static gboolean
++_client_register (GsdSubscriptionManager *manager,
++ const gchar *hostname,
++ const gchar *organisation,
++ const gchar *username,
++ const gchar *password,
++ GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GSubprocess) subprocess = NULL;
++
++ /* fallback */
++ if (organisation == NULL)
++ organisation = "";
++
++ /* apparently: "we can't send registration credentials over the regular
++ * system or session bus since those aren't really locked down..." */
++ if (!_client_register_start (manager, error))
++ return FALSE;
++ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
++ "pkexec", LIBEXECDIR "/gsd-subman-helper",
++ "--kind", "register-with-username",
++ "--address", priv->address,
++ "--hostname", hostname,
++ "--organisation", organisation,
++ "--username", username,
++ "--password", password,
++ NULL);
++ if (subprocess == NULL) {
++ g_prefix_error (error, "failed to find pkexec: ");
++ return FALSE;
++ }
++ if (!_client_subprocess_wait_check (subprocess, error))
++ return FALSE;
++
++ /* FIXME: also do on error? */
++ if (!_client_register_stop (manager, error))
++ return FALSE;
++ if (!_client_subscription_status_update (manager, error))
++ return FALSE;
++ _client_maybe__show_notification (manager);
++ return TRUE;
++}
++
++static gboolean
++_client_unregister (GsdSubscriptionManager *manager, GError **error)
++{
++ g_autoptr(GSubprocess) subprocess = NULL;
++
++ /* apparently: "we can't send registration credentials over the regular
++ * system or session bus since those aren't really locked down..." */
++ if (!_client_register_start (manager, error))
++ return FALSE;
++ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
++ "pkexec", LIBEXECDIR "/gsd-subman-helper",
++ "--kind", "unregister",
++ NULL);
++ if (subprocess == NULL) {
++ g_prefix_error (error, "failed to find pkexec: ");
++ return FALSE;
++ }
++ if (!_client_subprocess_wait_check (subprocess, error))
++ return FALSE;
++ if (!_client_subscription_status_update (manager, error))
++ return FALSE;
++ _client_maybe__show_notification (manager);
++ return TRUE;
++}
++
++static gboolean
++_client_update_config (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GVariant) val = NULL;
++ g_autoptr(GVariant) val_server = NULL;
++ g_autoptr(GVariantDict) dict = NULL;
++ GVariantIter iter;
++ gchar *key;
++ gchar *value;
++
++ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_CONFIG],
++ "GetAll",
++ g_variant_new ("(s)", priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ dict = g_variant_dict_new (g_variant_get_child_value (val, 0));
++ val_server = g_variant_dict_lookup_value (dict, "server", G_VARIANT_TYPE("a{ss}"));
++ if (val_server != NULL) {
++ g_variant_iter_init (&iter, val_server);
++ while (g_variant_iter_next (&iter, "{ss}", &key, &value)) {
++ g_debug ("%s=%s", key, value);
++ g_hash_table_insert (priv->config,
++ g_steal_pointer (&key),
++ g_steal_pointer (&value));
++ }
++ }
++ return TRUE;
++}
++
++static void
++_subman_proxy_signal_cb (GDBusProxy *proxy,
++ const gchar *sender_name,
++ const gchar *signal_name,
++ GVariant *parameters,
++ GsdSubscriptionManager *manager)
++{
++ g_autoptr(GError) error = NULL;
++ if (!_client_syspurpose_update (manager, &error)) {
++ g_warning ("failed to update syspurpose: %s", error->message);
++ g_clear_error (&error);
++ }
++ if (!_client_subscription_status_update (manager, &error)) {
++ g_warning ("failed to update subscription status: %s", error->message);
++ g_clear_error (&error);
++ }
++ _client_maybe__show_notification (manager);
++}
++
++static void
++_client_unload (GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ for (guint i = 0; i < _RHSM_INTERFACE_LAST; i++)
++ g_clear_object (&priv->proxies[i]);
++ g_hash_table_unref (priv->config);
++}
++
++static const gchar *
++_rhsm_interface_to_string (_RhsmInterface kind)
++{
++ if (kind == _RHSM_INTERFACE_CONFIG)
++ return "Config";
++ if (kind == _RHSM_INTERFACE_REGISTER_SERVER)
++ return "RegisterServer";
++ if (kind == _RHSM_INTERFACE_ATTACH)
++ return "Attach";
++ if (kind == _RHSM_INTERFACE_ENTITLEMENT)
++ return "Entitlement";
++ if (kind == _RHSM_INTERFACE_PRODUCTS)
++ return "Products";
++ if (kind == _RHSM_INTERFACE_CONSUMER)
++ return "Consumer";
++ if (kind == _RHSM_INTERFACE_SYSPURPOSE)
++ return "Syspurpose";
++ return NULL;
++}
++
++static gboolean
++_client_load (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++
++ priv->config = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
++
++ /* connect to all the interfaces on the *different* objects :| */
++ for (guint i = 0; i < _RHSM_INTERFACE_LAST; i++) {
++ const gchar *kind = _rhsm_interface_to_string (i);
++ g_autofree gchar *opath = g_strdup_printf ("/com/redhat/RHSM1/%s", kind);
++ g_autofree gchar *iface = g_strdup_printf ("com.redhat.RHSM1.%s", kind);
++ priv->proxies[i] =
++ g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
++ G_DBUS_PROXY_FLAGS_NONE,
++ NULL,
++ "com.redhat.RHSM1",
++ opath, iface,
++ NULL,
++ error);
++ if (priv->proxies[i] == NULL)
++ return FALSE;
++ /* we want to get notified if the status of the system changes */
++ g_signal_connect (priv->proxies[i], "g-signal",
++ G_CALLBACK (_subman_proxy_signal_cb), manager);
++ }
++
++ /* get initial status */
++ priv->userlang = "";
++ if (!_client_update_config (manager, error))
++ return FALSE;
++ if (!_client_subscription_status_update (manager, error))
++ return FALSE;
++ if (!_client_syspurpose_update (manager, error))
++ return FALSE;
++
++ /* success */
++ return TRUE;
++}
++
++gboolean
++gsd_subscription_manager_start (GsdSubscriptionManager *manager, GError **error)
++{
++ gboolean ret;
++ g_debug ("Starting subscription manager");
++ gnome_settings_profile_start (NULL);
++ ret = _client_load (manager, error);
++ _client_maybe__show_notification (manager);
++ gnome_settings_profile_end (NULL);
++ return ret;
++}
++
++void
++gsd_subscription_manager_stop (GsdSubscriptionManager *manager)
++{
++ g_debug ("Stopping subscription manager");
++ _client_unload (manager);
++}
++
++static void
++gsd_subscription_manager_class_init (GsdSubscriptionManagerClass *klass)
++{
++ GObjectClass *object_class = G_OBJECT_CLASS (klass);
++ object_class->finalize = gsd_subscription_manager_finalize;
++ notify_init ("gnome-settings-daemon");
++ g_type_class_add_private (klass, sizeof (GsdSubscriptionManagerPrivate));
++}
++
++static void
++_launch_info_overview (void)
++{
++ const gchar *argv[] = { "gnome-control-center", "info-overview", NULL };
++ g_debug ("Running gnome-control-center info-overview");
++ g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
++ NULL, NULL, NULL, NULL);
++}
++
++static void
++_notify_closed_cb (NotifyNotification *notification, gpointer user_data)
++{
++ /* FIXME: only launch when clicking on the main body, not the window close */
++ if (notify_notification_get_closed_reason (notification) == 0x400)
++ _launch_info_overview ();
++}
++
++static void
++_notify_clicked_cb (NotifyNotification *notification, char *action, gpointer user_data)
++{
++ _launch_info_overview ();
++}
++
++static void
++gsd_subscription_manager_init (GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv = GSD_SUBSCRIPTION_MANAGER_GET_PRIVATE (manager);
++
++ priv->timer_last_notified = g_timer_new ();
++
++ /* expired */
++ priv->notification_expired =
++ notify_notification_new (_("Subscription Has Expired"),
++ _("Add or renew a subscription to continue receiving software updates."),
++ NULL);
++ notify_notification_set_app_name (priv->notification_expired, _("Subscription"));
++ notify_notification_set_hint_string (priv->notification_expired, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_expired, "x-gnome-privacy-scope", "system");
++ notify_notification_set_urgency (priv->notification_expired, NOTIFY_URGENCY_CRITICAL);
++ notify_notification_add_action (priv->notification_expired,
++ "info-overview", _("Subscribe System…"),
++ _notify_clicked_cb,
++ manager, NULL);
++ g_signal_connect (priv->notification_expired, "closed",
++ G_CALLBACK (_notify_closed_cb), manager);
++
++ /* registered */
++ priv->notification_registered =
++ notify_notification_new (_("Registration Successful"),
++ _("The system has been registered and software updates have been enabled."),
++ NULL);
++ notify_notification_set_app_name (priv->notification_registered, _("Subscription"));
++ notify_notification_set_hint_string (priv->notification_registered, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_registered, "x-gnome-privacy-scope", "system");
++ notify_notification_set_urgency (priv->notification_registered, NOTIFY_URGENCY_CRITICAL);
++ g_signal_connect (priv->notification_registered, "closed",
++ G_CALLBACK (_notify_closed_cb), manager);
++
++ /* registration required */
++ priv->notification_registration_required =
++ notify_notification_new (_("System Not Registered"),
++ _("Please register your system to receive software updates."),
++ NULL);
++ notify_notification_set_app_name (priv->notification_registration_required, _("Subscription"));
++ notify_notification_set_hint_string (priv->notification_registration_required, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_registration_required, "x-gnome-privacy-scope", "system");
++ notify_notification_set_urgency (priv->notification_registration_required, NOTIFY_URGENCY_CRITICAL);
++ notify_notification_add_action (priv->notification_registration_required,
++ "info-overview", _("Register System…"),
++ _notify_clicked_cb,
++ manager, NULL);
++ g_signal_connect (priv->notification_registration_required, "closed",
++ G_CALLBACK (_notify_closed_cb), manager);
++}
++
++static void
++gsd_subscription_manager_finalize (GObject *object)
++{
++ GsdSubscriptionManager *manager;
++
++ g_return_if_fail (object != NULL);
++ g_return_if_fail (GSD_IS_SUBSCRIPTION_MANAGER (object));
++
++ manager = GSD_SUBSCRIPTION_MANAGER (object);
++
++ gsd_subscription_manager_stop (manager);
++
++ if (manager->priv->bus_cancellable != NULL) {
++ g_cancellable_cancel (manager->priv->bus_cancellable);
++ g_clear_object (&manager->priv->bus_cancellable);
++ }
++
++ g_clear_pointer (&manager->priv->introspection_data, g_dbus_node_info_unref);
++ g_clear_object (&manager->priv->connection);
++ g_clear_object (&manager->priv->notification_expired);
++ g_clear_object (&manager->priv->notification_registered);
++ g_timer_destroy (manager->priv->timer_last_notified);
++
++ if (manager->priv->name_id != 0) {
++ g_bus_unown_name (manager->priv->name_id);
++ manager->priv->name_id = 0;
++ }
++
++ G_OBJECT_CLASS (gsd_subscription_manager_parent_class)->finalize (object);
++}
++
++static void
++handle_method_call (GDBusConnection *connection,
++ const gchar *sender,
++ const gchar *object_path,
++ const gchar *interface_name,
++ const gchar *method_name,
++ GVariant *parameters,
++ GDBusMethodInvocation *invocation,
++ gpointer user_data)
++{
++ GsdSubscriptionManager *manager = GSD_SUBSCRIPTION_MANAGER (user_data);
++ g_autoptr(GError) error = NULL;
++
++ if (g_strcmp0 (method_name, "Register") == 0) {
++ const gchar *organisation = NULL;
++ const gchar *hostname = NULL;
++
++ if (FALSE) {
++ g_dbus_method_invocation_return_error_literal (invocation,
++ G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
++ "Cannot register at this time");
++
++ return;
++ }
++
++ g_autoptr(GVariantDict) dict = g_variant_dict_new (g_variant_get_child_value (parameters, 0));
++
++ const gchar *kind = NULL;
++ if (!g_variant_dict_lookup (dict, "kind", "&s", &kind)) {
++ g_dbus_method_invocation_return_error_literal (invocation,
++ G_IO_ERROR, G_IO_ERROR_FAILED,
++ "No kind specified");
++
++ return;
++ }
++ if (g_strcmp0 (kind, "username") == 0) {
++ const gchar *username = NULL;
++ const gchar *password = NULL;
++ g_variant_dict_lookup (dict, "hostname", "&s", &hostname);
++ g_variant_dict_lookup (dict, "organisation", "&s", &organisation);
++ g_variant_dict_lookup (dict, "username", "&s", &username);
++ g_variant_dict_lookup (dict, "password", "&s", &password);
++ if (!_client_register (manager,
++ hostname,
++ organisation,
++ username,
++ password,
++ &error)) {
++ g_dbus_method_invocation_return_gerror (invocation, error);
++ return;
++ }
++ } else if (g_strcmp0 (kind, "key") == 0) {
++ const gchar *activation_key = NULL;
++ g_variant_dict_lookup (dict, "hostname", "&s", &hostname);
++ g_variant_dict_lookup (dict, "organisation", "&s", &organisation);
++ g_variant_dict_lookup (dict, "activation-key", "&s", &activation_key);
++ if (!_client_register_with_keys (manager,
++ hostname,
++ organisation,
++ activation_key,
++ &error)) {
++ g_dbus_method_invocation_return_gerror (invocation, error);
++ return;
++ }
++ } else {
++ g_dbus_method_invocation_return_error_literal (invocation,
++ G_IO_ERROR, G_IO_ERROR_FAILED,
++ "Invalid kind specified");
++
++ return;
++ }
++ g_dbus_method_invocation_return_value (invocation, NULL);
++ } else if (g_strcmp0 (method_name, "Unregister") == 0) {
++ if (!_client_unregister (manager, &error)) {
++ g_dbus_method_invocation_return_gerror (invocation, error);
++ return;
++ }
++ g_dbus_method_invocation_return_value (invocation, NULL);
++ } else {
++ g_assert_not_reached ();
++ }
++}
++
++static GVariant *
++handle_get_property (GDBusConnection *connection,
++ const gchar *sender,
++ const gchar *object_path,
++ const gchar *interface_name,
++ const gchar *property_name,
++ GError **error, gpointer user_data)
++{
++ GsdSubscriptionManager *manager = GSD_SUBSCRIPTION_MANAGER (user_data);
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++
++ if (g_strcmp0 (interface_name, GSD_SUBSCRIPTION_DBUS_INTERFACE) != 0) {
++ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
++ "No such interface: %s", interface_name);
++ return NULL;
++ }
++
++ if (g_strcmp0 (property_name, "SubscriptionStatus") == 0)
++ return g_variant_new_uint32 (priv->subscription_status);
++
++ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
++ "Failed to get property: %s", property_name);
++ return NULL;
++}
++
++static gboolean
++handle_set_property (GDBusConnection *connection,
++ const gchar *sender,
++ const gchar *object_path,
++ const gchar *interface_name,
++ const gchar *property_name,
++ GVariant *value,
++ GError **error, gpointer user_data)
++{
++ if (g_strcmp0 (interface_name, GSD_SUBSCRIPTION_DBUS_INTERFACE) != 0) {
++ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
++ "No such interface: %s", interface_name);
++ return FALSE;
++ }
++ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
++ "No such property: %s", property_name);
++ return FALSE;
++}
++
++static const GDBusInterfaceVTable interface_vtable =
++{
++ handle_method_call,
++ handle_get_property,
++ handle_set_property
++};
++
++static void
++name_lost_handler_cb (GDBusConnection *connection, const gchar *name, gpointer user_data)
++{
++ g_debug ("lost name, so exiting");
++ gtk_main_quit ();
++}
++
++static void
++on_bus_gotten (GObject *source_object, GAsyncResult *res, GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ GDBusConnection *connection;
++ g_autoptr(GError) error = NULL;
++
++ connection = g_bus_get_finish (res, &error);
++ if (connection == NULL) {
++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
++ g_warning ("Could not get session bus: %s", error->message);
++ return;
++ }
++
++ priv->connection = connection;
++ g_dbus_connection_register_object (connection,
++ GSD_SUBSCRIPTION_DBUS_PATH,
++ priv->introspection_data->interfaces[0],
++ &interface_vtable,
++ manager,
++ NULL,
++ NULL);
++ priv->name_id = g_bus_own_name_on_connection (connection,
++ GSD_SUBSCRIPTION_DBUS_NAME,
++ G_BUS_NAME_OWNER_FLAGS_NONE,
++ NULL,
++ name_lost_handler_cb,
++ manager,
++ NULL);
++}
++
++static void
++register_manager_dbus (GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++
++ priv->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
++ g_assert (priv->introspection_data != NULL);
++ priv->bus_cancellable = g_cancellable_new ();
++
++ g_bus_get (G_BUS_TYPE_SESSION, priv->bus_cancellable,
++ (GAsyncReadyCallback) on_bus_gotten, manager);
++}
++
++GsdSubscriptionManager *
++gsd_subscription_manager_new (void)
++{
++ if (manager_object != NULL) {
++ g_object_ref (manager_object);
++ } else {
++ manager_object = g_object_new (GSD_TYPE_SUBSCRIPTION_MANAGER, NULL);
++ g_object_add_weak_pointer (manager_object,
++ (gpointer *) &manager_object);
++ register_manager_dbus (manager_object);
++ }
++
++ return GSD_SUBSCRIPTION_MANAGER (manager_object);
++}
+diff --git a/plugins/subman/gsd-subscription-manager.h b/plugins/subman/gsd-subscription-manager.h
+new file mode 100644
+index 00000000..6a524b1b
+--- /dev/null
++++ b/plugins/subman/gsd-subscription-manager.h
+@@ -0,0 +1,63 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
++ *
++ * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
++ *
++ * 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, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#ifndef __GSD_SUBSCRIPTION_MANAGER_H
++#define __GSD_SUBSCRIPTION_MANAGER_H
++
++#include <glib-object.h>
++
++G_BEGIN_DECLS
++
++#define GSD_TYPE_SUBSCRIPTION_MANAGER (gsd_subscription_manager_get_type ())
++#define GSD_SUBSCRIPTION_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManager))
++#define GSD_SUBSCRIPTION_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerClass))
++#define GSD_IS_SUBSCRIPTION_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_SUBSCRIPTION_MANAGER))
++#define GSD_IS_SUBSCRIPTION_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_SUBSCRIPTION_MANAGER))
++#define GSD_SUBSCRIPTION_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_SUBSCRIPTION_MANAGER, GsdSubscriptionManagerClass))
++#define GSD_SUBSCRIPTION_MANAGER_ERROR (gsd_subscription_manager_error_quark ())
++
++typedef struct GsdSubscriptionManagerPrivate GsdSubscriptionManagerPrivate;
++
++typedef struct
++{
++ GObject parent;
++ GsdSubscriptionManagerPrivate *priv;
++} GsdSubscriptionManager;
++
++typedef struct
++{
++ GObjectClass parent_class;
++} GsdSubscriptionManagerClass;
++
++enum
++{
++ GSD_SUBSCRIPTION_MANAGER_ERROR_FAILED
++};
++
++GType gsd_subscription_manager_get_type (void);
++GQuark gsd_subscription_manager_error_quark (void);
++
++GsdSubscriptionManager *gsd_subscription_manager_new (void);
++gboolean gsd_subscription_manager_start (GsdSubscriptionManager *manager,
++ GError **error);
++void gsd_subscription_manager_stop (GsdSubscriptionManager *manager);
++
++G_END_DECLS
++
++#endif /* __GSD_SUBSCRIPTION_MANAGER_H */
+diff --git a/plugins/subman/main.c b/plugins/subman/main.c
+new file mode 100644
+index 00000000..28ac995b
+--- /dev/null
++++ b/plugins/subman/main.c
+@@ -0,0 +1,8 @@
++#define NEW gsd_subscription_manager_new
++#define START gsd_subscription_manager_start
++#define STOP gsd_subscription_manager_stop
++#define MANAGER GsdSubscriptionManager
++#define GDK_BACKEND "x11"
++#include "gsd-subscription-manager.h"
++
++#include "daemon-skeleton-gtk.h"
+diff --git a/plugins/subman/meson.build b/plugins/subman/meson.build
+new file mode 100644
+index 00000000..bfd073b6
+--- /dev/null
++++ b/plugins/subman/meson.build
+@@ -0,0 +1,56 @@
++sources = files(
++ 'gsd-subscription-manager.c',
++ 'gsd-subman-common.c',
++ 'main.c'
++)
++
++deps = plugins_deps + [
++ libnotify_dep,
++ gtk_dep,
++ jsonglib_dep,
++ m_dep,
++]
++
++cflags += ['-DBINDIR="@0@"'.format(gsd_bindir)]
++cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)]
++
++executable(
++ 'gsd-' + plugin_name,
++ sources,
++ include_directories: [top_inc, common_inc],
++ dependencies: deps,
++ c_args: cflags,
++ install: true,
++ install_rpath: gsd_pkglibdir,
++ install_dir: gsd_libexecdir
++)
++
++# .Register needs to be called from root as subman can't do PolicyKit...
++policy = 'org.gnome.settings-daemon.plugins.subman.policy'
++policy_in = configure_file(
++ input: policy + '.in.in',
++ output: policy + '.in',
++ configuration: plugins_conf
++)
++
++i18n.merge_file(
++ policy,
++ input: policy_in,
++ output: policy,
++ po_dir: po_dir,
++ install: true,
++ install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions')
++)
++
++install_data('org.gnome.settings-daemon.plugins.subman.rules',
++ install_dir : join_paths(gsd_datadir, 'polkit-1', 'rules.d'))
++
++executable(
++ 'gsd-subman-helper',
++ 'gsd-subman-helper.c',
++ include_directories: top_inc,
++ dependencies: [gio_dep, jsonglib_dep],
++ install: true,
++ install_rpath: gsd_pkglibdir,
++ install_dir: gsd_libexecdir
++)
+diff --git a/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in b/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
+new file mode 100644
+index 00000000..14fe5915
+--- /dev/null
++++ b/plugins/subman/org.gnome.SettingsDaemon.Subscription.desktop.in
+@@ -0,0 +1,9 @@
++[Desktop Entry]
++Type=Application
++Name=GNOME Settings Daemon's subscription manager plugin
++Exec=@libexecdir@/gsd-subman
++OnlyShowIn=GNOME;
++NoDisplay=true
++X-GNOME-Autostart-Phase=Initialization
++X-GNOME-Autostart-Notify=true
++X-GNOME-AutoRestart=true
+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
+new file mode 100644
+index 00000000..59e9fdd4
+--- /dev/null
++++ b/plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
+@@ -0,0 +1,27 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE policyconfig PUBLIC
++ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
++ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
++<policyconfig>
++
++ <!--
++ Policy definitions for gnome-settings-daemon system-wide actions.
++ Copyright (c) 2019 Richard Hughes <richard@hughsie.com>
++ -->
++
++ <vendor>GNOME Settings Daemon</vendor>
++ <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url>
++ <icon_name>emblem-synchronizing</icon_name>
++
++ <action id="org.gnome.settings-daemon.plugins.subman.register">
++ <description>Register the system</description>
++ <message>Authentication is required to register the system</message>
++ <defaults>
++ <allow_any>no</allow_any>
++ <allow_inactive>no</allow_inactive>
++ <allow_active>auth_admin_keep</allow_active>
++ </defaults>
++ <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-subman-helper</annotate>
++ </action>
++
++</policyconfig>
+diff --git a/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules b/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
+new file mode 100644
+index 00000000..1ed3a0ea
+--- /dev/null
++++ b/plugins/subman/org.gnome.settings-daemon.plugins.subman.rules
+@@ -0,0 +1,7 @@
++polkit.addRule(function(action, subject) {
++ if (action.id == "org.gnome.settings-daemon.plugins.subman.register" &&
++ subject.active == true && subject.local == true &&
++ subject.isInGroup("wheel")) {
++ return polkit.Result.YES;
++ }
++});
+--
+2.43.0
+
+
+From 401046df68e152ffd6bd0ec56bbc4361eb351b55 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Thu, 27 Jun 2019 16:12:00 +0200
+Subject: [PATCH 02/21] subman: Add InstalledProducts dbus property for g-c-c
+
+---
+ plugins/subman/gsd-subscription-manager.c | 135 ++++++++++++++++++++++
+ 1 file changed, 135 insertions(+)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 08b13fa6..a8c18a26 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -1,6 +1,7 @@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
++ * Copyright (C) 2019 Kalev Lember <klember@redhat.com>
+ *
+ * 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
+@@ -44,6 +45,7 @@ static const gchar introspection_xml[] =
+ " <arg type='a{sv}' name='options' direction='in'/>"
+ " </method>"
+ " <method name='Unregister'/>"
++" <property name='InstalledProducts' type='aa{sv}' access='read'/>"
+ " <property name='SubscriptionStatus' type='u' access='read'/>"
+ " </interface>"
+ "</node>";
+@@ -72,6 +74,7 @@ struct GsdSubscriptionManagerPrivate
+ GDBusProxy *proxies[_RHSM_INTERFACE_LAST];
+ const gchar *userlang; /* owned by GLib internally */
+ GHashTable *config; /* str:str */
++ GPtrArray *installed_products;
+ gchar *address;
+
+ GTimer *timer_last_notified;
+@@ -92,6 +95,32 @@ static void gsd_subscription_manager_finalize (GObject *objec
+
+ G_DEFINE_TYPE (GsdSubscriptionManager, gsd_subscription_manager, G_TYPE_OBJECT)
+
++typedef struct
++{
++ gchar *product_name;
++ gchar *product_id;
++ gchar *version;
++ gchar *arch;
++ gchar *status;
++ gchar *starts;
++ gchar *ends;
++} ProductData;
++
++static void
++product_data_free (ProductData *product)
++{
++ g_free (product->product_name);
++ g_free (product->product_id);
++ g_free (product->version);
++ g_free (product->arch);
++ g_free (product->status);
++ g_free (product->starts);
++ g_free (product->ends);
++ g_free (product);
++}
++
++G_DEFINE_AUTOPTR_CLEANUP_FUNC (ProductData, product_data_free);
++
+ static gpointer manager_object = NULL;
+
+ GQuark
+@@ -120,6 +149,32 @@ _client_subscription_status_from_text (const gchar *status_txt)
+ return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
+ }
+
++static GVariant *
++_make_installed_products_variant (GPtrArray *installed_products)
++{
++ GVariantBuilder builder;
++ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
++
++ for (guint i = 0; i < installed_products->len; i++) {
++ ProductData *product = g_ptr_array_index (installed_products, i);
++ g_auto(GVariantDict) dict;
++
++ g_variant_dict_init (&dict, NULL);
++
++ g_variant_dict_insert (&dict, "product-name", "s", product->product_name);
++ g_variant_dict_insert (&dict, "product-id", "s", product->product_id);
++ g_variant_dict_insert (&dict, "version", "s", product->version);
++ g_variant_dict_insert (&dict, "arch", "s", product->arch);
++ g_variant_dict_insert (&dict, "status", "s", product->status);
++ g_variant_dict_insert (&dict, "starts", "s", product->starts);
++ g_variant_dict_insert (&dict, "ends", "s", product->ends);
++
++ g_variant_builder_add_value (&builder, g_variant_dict_end (&dict));
++ }
++
++ return g_variant_builder_end (&builder);
++}
++
+ static void
+ _emit_property_changed (GsdSubscriptionManager *manager,
+ const gchar *property_name,
+@@ -154,6 +209,69 @@ _emit_property_changed (GsdSubscriptionManager *manager,
+ g_variant_builder_clear (&invalidated_builder);
+ }
+
++static gboolean
++_client_installed_products_update (GsdSubscriptionManager *manager, GError **error)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ JsonNode *json_root;
++ JsonArray *json_products_array;
++ const gchar *json_txt = NULL;
++ g_autoptr(GVariant) val = NULL;
++ g_autoptr(JsonParser) json_parser = json_parser_new ();
++
++ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_PRODUCTS],
++ "ListInstalledProducts",
++ g_variant_new ("(sa{sv}s)",
++ "" /* filter_string */,
++ NULL /* proxy_options */,
++ priv->userlang),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (val == NULL)
++ return FALSE;
++ g_variant_get (val, "(&s)", &json_txt);
++ g_debug ("Products.ListInstalledProducts JSON: %s", json_txt);
++ if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
++ return FALSE;
++ json_root = json_parser_get_root (json_parser);
++ json_products_array = json_node_get_array (json_root);
++ if (json_products_array == NULL) {
++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
++ "no InstalledProducts array in %s", json_txt);
++ return FALSE;
++ }
++
++ g_ptr_array_set_size (priv->installed_products, 0);
++
++ for (guint i = 0; i < json_array_get_length (json_products_array); i++) {
++ JsonArray *json_product = json_array_get_array_element (json_products_array, i);
++ g_autoptr(ProductData) product = g_new0 (ProductData, 1);
++
++ if (json_product == NULL)
++ continue;
++ if (json_array_get_length (json_product) < 8) {
++ g_debug ("Unexpected number of array elements in InstalledProducts JSON");
++ continue;
++ }
++
++ product->product_name = g_strdup (json_array_get_string_element (json_product, 0));
++ product->product_id = g_strdup (json_array_get_string_element (json_product, 1));
++ product->version = g_strdup (json_array_get_string_element (json_product, 2));
++ product->arch = g_strdup (json_array_get_string_element (json_product, 3));
++ product->status = g_strdup (json_array_get_string_element (json_product, 4));
++ product->starts = g_strdup (json_array_get_string_element (json_product, 6));
++ product->ends = g_strdup (json_array_get_string_element (json_product, 7));
++
++ g_ptr_array_add (priv->installed_products, g_steal_pointer (&product));
++ }
++
++ /* emit notification for g-c-c */
++ _emit_property_changed (manager, "InstalledProducts",
++ _make_installed_products_variant (priv->installed_products));
++
++ return TRUE;
++}
++
+ static gboolean
+ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **error)
+ {
+@@ -450,6 +568,8 @@ _client_register_with_keys (GsdSubscriptionManager *manager,
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
++ if (!_client_installed_products_update (manager, error))
++ return FALSE;
+ _client_maybe__show_notification (manager);
+
+ /* success */
+@@ -497,6 +617,8 @@ _client_register (GsdSubscriptionManager *manager,
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
++ if (!_client_installed_products_update (manager, error))
++ return FALSE;
+ _client_maybe__show_notification (manager);
+ return TRUE;
+ }
+@@ -523,6 +645,8 @@ _client_unregister (GsdSubscriptionManager *manager, GError **error)
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
++ if (!_client_installed_products_update (manager, error))
++ return FALSE;
+ _client_maybe__show_notification (manager);
+ return TRUE;
+ }
+@@ -575,6 +699,10 @@ _subman_proxy_signal_cb (GDBusProxy *proxy,
+ g_warning ("failed to update subscription status: %s", error->message);
+ g_clear_error (&error);
+ }
++ if (!_client_installed_products_update (manager, &error)) {
++ g_warning ("failed to update installed products: %s", error->message);
++ g_clear_error (&error);
++ }
+ _client_maybe__show_notification (manager);
+ }
+
+@@ -640,6 +768,8 @@ _client_load (GsdSubscriptionManager *manager, GError **error)
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
++ if (!_client_installed_products_update (manager, error))
++ return FALSE;
+ if (!_client_syspurpose_update (manager, error))
+ return FALSE;
+
+@@ -703,6 +833,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv = GSD_SUBSCRIPTION_MANAGER_GET_PRIVATE (manager);
+
++ priv->installed_products = g_ptr_array_new_with_free_func ((GDestroyNotify) product_data_free);
+ priv->timer_last_notified = g_timer_new ();
+
+ /* expired */
+@@ -767,6 +898,7 @@ gsd_subscription_manager_finalize (GObject *object)
+ g_clear_object (&manager->priv->bus_cancellable);
+ }
+
++ g_clear_pointer (&manager->priv->installed_products, g_ptr_array_unref);
+ g_clear_pointer (&manager->priv->introspection_data, g_dbus_node_info_unref);
+ g_clear_object (&manager->priv->connection);
+ g_clear_object (&manager->priv->notification_expired);
+@@ -884,6 +1016,9 @@ handle_get_property (GDBusConnection *connection,
+ if (g_strcmp0 (property_name, "SubscriptionStatus") == 0)
+ return g_variant_new_uint32 (priv->subscription_status);
+
++ if (g_strcmp0 (property_name, "InstalledProducts") == 0)
++ return _make_installed_products_variant (priv->installed_products);
++
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Failed to get property: %s", property_name);
+ return NULL;
+--
+2.43.0
+
+
+From 3c367361c1b5f695a3070ce8c6344b633da7fac5 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Fri, 28 Jun 2019 18:10:36 +0200
+Subject: [PATCH 03/21] subman: Increase RHSM dbus call timeouts
+
+Increase the dbus timeouts to 5 minutes as the register/unregister calls
+seem to routinely take more than a minute.
+---
+ plugins/subman/gsd-subman-helper.c | 17 ++++++++++++-----
+ 1 file changed, 12 insertions(+), 5 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index b1ea48a3..2218abed 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -28,6 +28,8 @@
+ #include <gio/gio.h>
+ #include <json-glib/json-glib.h>
+
++#define DBUS_TIMEOUT 300000 /* 5 minutes */
++
+ static void
+ _helper_convert_error (const gchar *json_txt, GError **error)
+ {
+@@ -94,7 +96,8 @@ _helper_unregister (GError **error)
+ proxy_options,
+ ""), /* lang */
+ G_DBUS_CALL_FLAGS_NONE,
+- -1, NULL, error);
++ DBUS_TIMEOUT,
++ NULL, error);
+ return res != NULL;
+ }
+
+@@ -127,7 +130,8 @@ _helper_auto_attach (GError **error)
+ proxy_options,
+ ""), /* lang */
+ G_DBUS_CALL_FLAGS_NONE,
+- -1, NULL, error);
++ DBUS_TIMEOUT,
++ NULL, error);
+ if (res == NULL)
+ return FALSE;
+ g_variant_get (res, "(&s)", &str);
+@@ -158,7 +162,8 @@ _helper_save_config (const gchar *key, const gchar *value, GError **error)
+ g_variant_new_string (value),
+ ""), /* lang */
+ G_DBUS_CALL_FLAGS_NONE,
+- -1, NULL, error);
++ DBUS_TIMEOUT,
++ NULL, error);
+ return res != NULL;
+ }
+
+@@ -307,7 +312,8 @@ main (int argc, char *argv[])
+ subman_conopts,
+ userlang),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+- -1, NULL, &error_local);
++ DBUS_TIMEOUT,
++ NULL, &error_local);
+ if (res == NULL) {
+ g_dbus_error_strip_remote_error (error_local);
+ _helper_convert_error (error_local->message, &error);
+@@ -341,7 +347,8 @@ main (int argc, char *argv[])
+ subman_conopts,
+ userlang),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+- -1, NULL, &error_local);
++ DBUS_TIMEOUT,
++ NULL, &error_local);
+ if (res == NULL) {
+ g_dbus_error_strip_remote_error (error_local);
+ _helper_convert_error (error_local->message, &error);
+--
+2.43.0
+
+
+From 5f57d9fff5dce2b06967cc5a1cd3328c072aa29d Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 20 Aug 2020 11:20:47 -0400
+Subject: [PATCH 04/21] subman: Drop userlang field
+
+It's currently always erroneously set to empty string.
+
+This commit drops it, and just uses "C.UTF-8" everywhere,
+which is what we actually want.
+---
+ plugins/subman/gsd-subscription-manager.c | 14 ++++++--------
+ 1 file changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index a8c18a26..46f051a5 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -72,7 +72,6 @@ struct GsdSubscriptionManagerPrivate
+ GCancellable *bus_cancellable;
+
+ GDBusProxy *proxies[_RHSM_INTERFACE_LAST];
+- const gchar *userlang; /* owned by GLib internally */
+ GHashTable *config; /* str:str */
+ GPtrArray *installed_products;
+ gchar *address;
+@@ -224,7 +223,7 @@ _client_installed_products_update (GsdSubscriptionManager *manager, GError **err
+ g_variant_new ("(sa{sv}s)",
+ "" /* filter_string */,
+ NULL /* proxy_options */,
+- priv->userlang),
++ "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -290,7 +289,7 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+ "GetStatus",
+ g_variant_new ("(ss)",
+ "", /* assumed as 'now' */
+- priv->userlang),
++ "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -332,7 +331,7 @@ _client_syspurpose_update (GsdSubscriptionManager *manager, GError **error)
+
+ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_SYSPURPOSE],
+ "GetSyspurpose",
+- g_variant_new ("(s)", priv->userlang),
++ g_variant_new ("(s)", "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -375,7 +374,7 @@ _client_register_start (GsdSubscriptionManager *manager, GError **error)
+ if (proxy == NULL)
+ return FALSE;
+ val = g_dbus_proxy_call_sync (proxy, "Start",
+- g_variant_new ("(s)", priv->userlang),
++ g_variant_new ("(s)", "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -408,7 +407,7 @@ _client_register_stop (GsdSubscriptionManager *manager, GError **error)
+ if (proxy == NULL)
+ return FALSE;
+ val = g_dbus_proxy_call_sync (proxy, "Stop",
+- g_variant_new ("(s)", priv->userlang),
++ g_variant_new ("(s)", "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -664,7 +663,7 @@ _client_update_config (GsdSubscriptionManager *manager, GError **error)
+
+ val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_CONFIG],
+ "GetAll",
+- g_variant_new ("(s)", priv->userlang),
++ g_variant_new ("(s)", "C.UTF-8"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, error);
+ if (val == NULL)
+@@ -763,7 +762,6 @@ _client_load (GsdSubscriptionManager *manager, GError **error)
+ }
+
+ /* get initial status */
+- priv->userlang = "";
+ if (!_client_update_config (manager, error))
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+--
+2.43.0
+
+
+From 91ffe536f3117483c49b33f2277ab4a0683c556b Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 15:04:17 -0500
+Subject: [PATCH 05/21] subman: Use user locale for registration/subscription
+ operations
+
+This makes sure that error messages are in the correct locale.
+---
+ plugins/subman/gsd-subman-helper.c | 17 +++++++++++------
+ 1 file changed, 11 insertions(+), 6 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index 2218abed..5eadd20e 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -24,11 +24,13 @@
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <stdlib.h>
++#include <locale.h>
+
+ #include <gio/gio.h>
+ #include <json-glib/json-glib.h>
+
+ #define DBUS_TIMEOUT 300000 /* 5 minutes */
++static const char *locale;
+
+ static void
+ _helper_convert_error (const gchar *json_txt, GError **error)
+@@ -94,7 +96,7 @@ _helper_unregister (GError **error)
+ "Unregister",
+ g_variant_new ("(a{sv}s)",
+ proxy_options,
+- ""), /* lang */
++ locale),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ NULL, error);
+@@ -128,7 +130,7 @@ _helper_auto_attach (GError **error)
+ g_variant_new ("(sa{sv}s)",
+ "", /* now? */
+ proxy_options,
+- ""), /* lang */
++ locale),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ NULL, error);
+@@ -160,7 +162,7 @@ _helper_save_config (const gchar *key, const gchar *value, GError **error)
+ g_variant_new ("(svs)",
+ key,
+ g_variant_new_string (value),
+- ""), /* lang */
++ locale),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+ NULL, error);
+@@ -170,7 +172,6 @@ _helper_save_config (const gchar *key, const gchar *value, GError **error)
+ int
+ main (int argc, char *argv[])
+ {
+- const gchar *userlang = ""; /* as root, so no translations */
+ g_autofree gchar *activation_key = NULL;
+ g_autofree gchar *address = NULL;
+ g_autofree gchar *hostname = NULL;
+@@ -218,6 +219,10 @@ main (int argc, char *argv[])
+ g_printerr ("This program can only be used by the root user\n");
+ return G_IO_ERROR_NOT_SUPPORTED;
+ }
++
++ setlocale (LC_ALL, "");
++ locale = setlocale (LC_MESSAGES, NULL);
++
+ g_option_context_add_main_entries (context, options, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_printerr ("Failed to parse arguments: %s\n", error->message);
+@@ -310,7 +315,7 @@ main (int argc, char *argv[])
+ activation_keys,
+ subman_options,
+ subman_conopts,
+- userlang),
++ locale),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ DBUS_TIMEOUT,
+ NULL, &error_local);
+@@ -345,7 +350,7 @@ main (int argc, char *argv[])
+ password,
+ subman_options,
+ subman_conopts,
+- userlang),
++ locale),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ DBUS_TIMEOUT,
+ NULL, &error_local);
+--
+2.43.0
+
+
+From e7dc41a1f82d3ec6daea40b01ff9e38a71748d96 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 20 Aug 2020 13:34:19 -0400
+Subject: [PATCH 06/21] subman: Handle subscription-manager giving invalid
+ status better
+
+subscription-manager potentially returns status messages that the
+subman plugin treats as enum values translated in some unknown
+other language. It could be tied to system locale, or due to a
+caching bug a previous locale used.
+
+This commit tries to work around that bug, by instead relying on
+the GetUUID() method and valid attribute. If there's no UUID we
+know the system is unregistered. If there's a UUID but the valid
+attribute is FALSE we know the system is registered, but hasn't
+got proper entitlements.
+---
+ plugins/subman/gsd-subscription-manager.c | 69 ++++++++++++-----------
+ 1 file changed, 36 insertions(+), 33 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 46f051a5..e2c16056 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -131,23 +131,6 @@ gsd_subscription_manager_error_quark (void)
+ return quark;
+ }
+
+-static GsdSubmanSubscriptionStatus
+-_client_subscription_status_from_text (const gchar *status_txt)
+-{
+- if (g_strcmp0 (status_txt, "Unknown") == 0)
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
+- if (g_strcmp0 (status_txt, "Current") == 0)
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID;
+- if (g_strcmp0 (status_txt, "Invalid") == 0)
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
+- if (g_strcmp0 (status_txt, "Disabled") == 0)
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED;
+- if (g_strcmp0 (status_txt, "Insufficient") == 0)
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID;
+- g_warning ("Unknown subscription status: %s", status_txt); // 'Current'?
+- return GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
+-}
+-
+ static GVariant *
+ _make_installed_products_variant (GPtrArray *installed_products)
+ {
+@@ -275,40 +258,60 @@ static gboolean
+ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **error)
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GVariant) uuid = NULL;
++ const gchar *uuid_txt = NULL;
+ JsonNode *json_root;
+ JsonObject *json_obj;
+ const gchar *json_txt = NULL;
+- const gchar *status_txt = NULL;
+- g_autoptr(GVariant) val = NULL;
++ g_autoptr(GVariant) status = NULL;
+ g_autoptr(JsonParser) json_parser = json_parser_new ();
+
+ /* save old value */
+ priv->subscription_status_last = priv->subscription_status;
+
+- val = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_ENTITLEMENT],
+- "GetStatus",
+- g_variant_new ("(ss)",
+- "", /* assumed as 'now' */
+- "C.UTF-8"),
+- G_DBUS_CALL_FLAGS_NONE,
+- -1, NULL, error);
+- if (val == NULL)
++ uuid = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_CONSUMER],
++ "GetUuid",
++ g_variant_new ("(s)",
++ "C.UTF-8"),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (uuid == NULL)
+ return FALSE;
+- g_variant_get (val, "(&s)", &json_txt);
++
++ g_variant_get (uuid, "(&s)", &uuid_txt);
++
++ status = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_ENTITLEMENT],
++ "GetStatus",
++ g_variant_new ("(ss)",
++ "", /* assumed as 'now' */
++ "C.UTF-8"),
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, error);
++ if (status == NULL)
++ return FALSE;
++ g_variant_get (status, "(&s)", &json_txt);
+ g_debug ("Entitlement.GetStatus JSON: %s", json_txt);
+ if (!json_parser_load_from_data (json_parser, json_txt, -1, error))
+ return FALSE;
+ json_root = json_parser_get_root (json_parser);
+ json_obj = json_node_get_object (json_root);
+- if (!json_object_has_member (json_obj, "status")) {
++ if (!json_object_has_member (json_obj, "valid")) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+- "no Entitlement.GetStatus status in %s", json_txt);
++ "no Entitlement.GetStatus valid in %s", json_txt);
+ return FALSE;
+ }
+
+- status_txt = json_object_get_string_member (json_obj, "status");
+- g_debug ("Entitlement.GetStatus: %s", status_txt);
+- priv->subscription_status = _client_subscription_status_from_text (status_txt);
++ gboolean is_valid = json_object_get_boolean_member (json_obj, "valid");
++
++ if (uuid_txt[0] != '\0') {
++ if (is_valid) {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID;
++ } else {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
++ }
++ } else {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
++ }
+
+ /* emit notification for g-c-c */
+ if (priv->subscription_status != priv->subscription_status_last) {
+--
+2.43.0
+
+
+From 0bbb5038a22c10d1187e063a4fd02dfdfe149497 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Tue, 25 Aug 2020 10:34:03 -0400
+Subject: [PATCH 07/21] subman: Force re-subscribe if the admin already
+ subscribed
+
+It's possible for an admin to to half-enroll the system with RHN,
+using the CLI tools.
+
+Meaning, it's possible for them to register the system with the
+service, but not attach to a purchased license for the machine,
+the, so called, entitlements.
+
+The subman module always does both halves of the registration process
+in lock step. This means, if an admin tries to register using GNOME
+while in a half-registered state, subman will fail because the first
+step, the registration step, is already finished.
+
+This commit addresses that problem by trying to unregister up front
+before registering.
+---
+ plugins/subman/gsd-subman-helper.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index 5eadd20e..bdcf42c4 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -78,7 +78,6 @@ _helper_unregister (GError **error)
+ g_autoptr(GVariantBuilder) proxy_options = NULL;
+ g_autoptr(GVariant) res = NULL;
+
+- g_debug ("unregistering");
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+@@ -235,6 +234,7 @@ main (int argc, char *argv[])
+ return G_IO_ERROR_INVALID_DATA;
+ }
+ if (g_strcmp0 (kind, "unregister") == 0) {
++ g_debug ("unregistering");
+ if (!_helper_unregister (&error)) {
+ g_printerr ("Failed to Unregister: %s\n", error->message);
+ return G_IO_ERROR_NOT_INITIALIZED;
+@@ -306,6 +306,9 @@ main (int argc, char *argv[])
+ return G_IO_ERROR_INVALID_DATA;
+ }
+
++ g_debug ("trying to unregister in case machine is already registered");
++ _helper_unregister (NULL);
++
+ g_debug ("registering using activation key");
+ activation_keys = g_strsplit (activation_key, ",", -1);
+ res = g_dbus_proxy_call_sync (proxy,
+@@ -329,7 +332,6 @@ main (int argc, char *argv[])
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GVariant) res = NULL;
+
+- g_debug ("registering using username and password");
+ if (username == NULL) {
+ g_printerr ("Required --username\n");
+ return G_IO_ERROR_INVALID_DATA;
+@@ -342,6 +344,11 @@ main (int argc, char *argv[])
+ g_printerr ("Required --organisation\n");
+ return G_IO_ERROR_INVALID_DATA;
+ }
++
++ g_debug ("trying to unregister in case machine is already registered");
++ _helper_unregister (NULL);
++
++ g_debug ("registering using username and password");
+ res = g_dbus_proxy_call_sync (proxy,
+ "Register",
+ g_variant_new ("(sssa{ss}a{ss}s)",
+--
+2.43.0
+
+
+From ad7c4f5953a2954ea62790d906fd699da55cc965 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Tue, 25 Aug 2020 16:20:42 -0400
+Subject: [PATCH 08/21] subman: Don't send secrets through command line
+
+The command line is introspectable with "ps", and it even gets logged
+to syslog, so it's not suitable for passing secrets.
+
+Unfortunately, the user's password is currently passed.
+
+This commit addresses that problem by passing the password through
+stdin, instead.
+---
+ plugins/subman/gsd-subman-helper.c | 32 ++++++++------
+ plugins/subman/gsd-subscription-manager.c | 52 ++++++++++++++++++++---
+ plugins/subman/meson.build | 2 +-
+ 3 files changed, 66 insertions(+), 20 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index bdcf42c4..66cf5415 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -21,12 +21,14 @@
+
+ #include "config.h"
+
++
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <stdlib.h>
+ #include <locale.h>
+
+ #include <gio/gio.h>
++#include <gio/gunixinputstream.h>
+ #include <json-glib/json-glib.h>
+
+ #define DBUS_TIMEOUT 300000 /* 5 minutes */
+@@ -176,7 +178,6 @@ main (int argc, char *argv[])
+ g_autofree gchar *hostname = NULL;
+ g_autofree gchar *kind = NULL;
+ g_autofree gchar *organisation = NULL;
+- g_autofree gchar *password = NULL;
+ g_autofree gchar *port = NULL;
+ g_autofree gchar *prefix = NULL;
+ g_autofree gchar *proxy_server = NULL;
+@@ -188,6 +189,7 @@ main (int argc, char *argv[])
+ g_autoptr(GVariantBuilder) proxy_options = NULL;
+ g_autoptr(GVariantBuilder) subman_conopts = NULL;
+ g_autoptr(GVariantBuilder) subman_options = NULL;
++ g_autoptr(GInputStream) standard_input_stream = g_unix_input_stream_new (STDIN_FILENO, FALSE);
+
+ const GOptionEntry options[] = {
+ { "kind", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+@@ -196,12 +198,8 @@ main (int argc, char *argv[])
+ &address, "UNIX address", NULL },
+ { "username", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+ &username, "Username", NULL },
+- { "password", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+- &password, "Password", NULL },
+ { "organisation", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+ &organisation, "Organisation", NULL },
+- { "activation-key", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+- &activation_key, "Activation keys", NULL },
+ { "hostname", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
+ &hostname, "Registration server hostname", NULL },
+ { "prefix", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
+@@ -296,16 +294,20 @@ main (int argc, char *argv[])
+ g_auto(GStrv) activation_keys = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GVariant) res = NULL;
++ gchar activation_key[PIPE_BUF + 1] = "";
+
+- if (activation_key == NULL) {
+- g_printerr ("Required --activation-key\n");
+- return G_IO_ERROR_INVALID_DATA;
+- }
+ if (organisation == NULL) {
+ g_printerr ("Required --organisation\n");
+ return G_IO_ERROR_INVALID_DATA;
+ }
+
++ g_input_stream_read (standard_input_stream, activation_key, sizeof (activation_key) - 1, NULL, &error_local);
++
++ if (error_local != NULL) {
++ g_printerr ("Could not read activation key: %s\n", error_local->message);
++ return G_IO_ERROR_INVALID_DATA;
++ }
++
+ g_debug ("trying to unregister in case machine is already registered");
+ _helper_unregister (NULL);
+
+@@ -331,20 +333,24 @@ main (int argc, char *argv[])
+ } else if (g_strcmp0 (kind, "register-with-username") == 0) {
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GVariant) res = NULL;
++ gchar password[PIPE_BUF + 1] = "";
+
+ if (username == NULL) {
+ g_printerr ("Required --username\n");
+ return G_IO_ERROR_INVALID_DATA;
+ }
+- if (password == NULL) {
+- g_printerr ("Required --password\n");
+- return G_IO_ERROR_INVALID_DATA;
+- }
+ if (organisation == NULL) {
+ g_printerr ("Required --organisation\n");
+ return G_IO_ERROR_INVALID_DATA;
+ }
+
++ g_input_stream_read (standard_input_stream, password, sizeof (password) - 1, NULL, &error_local);
++
++ if (error_local != NULL) {
++ g_printerr ("Could not read password: %s\n", error_local->message);
++ return G_IO_ERROR_INVALID_DATA;
++ }
++
+ g_debug ("trying to unregister in case machine is already registered");
+ _helper_unregister (NULL);
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index e2c16056..0838d490 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -21,6 +21,7 @@
+ #include "config.h"
+
+ #include <glib/gi18n.h>
++#include <gio/gunixinputstream.h>
+ #include <gdk/gdk.h>
+ #include <gtk/gtk.h>
+ #include <json-glib/json-glib.h>
+@@ -544,26 +545,45 @@ _client_register_with_keys (GsdSubscriptionManager *manager,
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv;
+ g_autoptr(GSubprocess) subprocess = NULL;
++ g_autoptr(GBytes) stdin_buf = g_bytes_new (activation_key, strlen (activation_key) + 1);
++ g_autoptr(GBytes) stderr_buf = NULL;
++ gint rc;
+
+ /* apparently: "we can't send registration credentials over the regular
+ * system or session bus since those aren't really locked down..." */
+ if (!_client_register_start (manager, error))
+ return FALSE;
+ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
+- subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
+ "pkexec", LIBEXECDIR "/gsd-subman-helper",
+ "--kind", "register-with-key",
+ "--address", priv->address,
+ "--hostname", hostname,
+ "--organisation", organisation,
+- "--activation-key", activation_key,
+ NULL);
+ if (subprocess == NULL) {
+ g_prefix_error (error, "failed to find pkexec: ");
+ return FALSE;
+ }
+- if (!_client_subprocess_wait_check (subprocess, error))
++
++ if (!g_subprocess_communicate (subprocess, stdin_buf, NULL, NULL, &stderr_buf, error)) {
++ g_prefix_error (error, "failed to run pkexec: ");
+ return FALSE;
++ }
++
++ rc = g_subprocess_get_exit_status (subprocess);
++ if (rc != 0) {
++ if (g_bytes_get_size (stderr_buf) == 0) {
++ g_set_error_literal (error, G_IO_ERROR, rc,
++ "Failed to run helper without stderr");
++ return FALSE;
++ }
++
++ g_set_error (error, G_IO_ERROR, rc,
++ "%.*s",
++ g_bytes_get_size (stderr_buf),
++ g_bytes_get_data (stderr_buf, NULL));
++ }
+
+ /* FIXME: also do on error? */
+ if (!_client_register_stop (manager, error))
+@@ -588,6 +608,9 @@ _client_register (GsdSubscriptionManager *manager,
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv;
+ g_autoptr(GSubprocess) subprocess = NULL;
++ g_autoptr(GBytes) stdin_buf = g_bytes_new (password, strlen (password) + 1);
++ g_autoptr(GBytes) stderr_buf = NULL;
++ gint rc;
+
+ /* fallback */
+ if (organisation == NULL)
+@@ -598,21 +621,38 @@ _client_register (GsdSubscriptionManager *manager,
+ if (!_client_register_start (manager, error))
+ return FALSE;
+ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
+- subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE, error,
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE,
++ error,
+ "pkexec", LIBEXECDIR "/gsd-subman-helper",
+ "--kind", "register-with-username",
+ "--address", priv->address,
+ "--hostname", hostname,
+ "--organisation", organisation,
+ "--username", username,
+- "--password", password,
+ NULL);
+ if (subprocess == NULL) {
+ g_prefix_error (error, "failed to find pkexec: ");
+ return FALSE;
+ }
+- if (!_client_subprocess_wait_check (subprocess, error))
++
++ if (!g_subprocess_communicate (subprocess, stdin_buf, NULL, NULL, &stderr_buf, error)) {
++ g_prefix_error (error, "failed to run pkexec: ");
+ return FALSE;
++ }
++
++ rc = g_subprocess_get_exit_status (subprocess);
++ if (rc != 0) {
++ if (g_bytes_get_size (stderr_buf) == 0) {
++ g_set_error_literal (error, G_IO_ERROR, rc,
++ "Failed to run helper without stderr");
++ return FALSE;
++ }
++
++ g_set_error (error, G_IO_ERROR, rc,
++ "%.*s",
++ g_bytes_get_size (stderr_buf),
++ g_bytes_get_data (stderr_buf, NULL));
++ }
+
+ /* FIXME: also do on error? */
+ if (!_client_register_stop (manager, error))
+diff --git a/plugins/subman/meson.build b/plugins/subman/meson.build
+index bfd073b6..e4b4589d 100644
+--- a/plugins/subman/meson.build
++++ b/plugins/subman/meson.build
+@@ -49,7 +49,7 @@ executable(
+ 'gsd-subman-helper',
+ 'gsd-subman-helper.c',
+ include_directories: top_inc,
+- dependencies: [gio_dep, jsonglib_dep],
++ dependencies: [gio_dep, gio_unix_dep, jsonglib_dep],
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+--
+2.43.0
+
+
+From 2b10a7ed9209ef9b542c014eba2688363e6c8483 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 21 Jan 2021 09:52:19 -0500
+Subject: [PATCH 09/21] subman: Don't treat failure to attach as fatal
+
+Many organizations don't require specific subscriptions to get
+updates (called "simple content access"). At the moment,
+those systems get an error when registering.
+
+This commit quiets the error.
+---
+ plugins/subman/gsd-subman-helper.c | 46 ++++++++++++++++++++++++------
+ 1 file changed, 37 insertions(+), 9 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index 66cf5415..2152c521 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -52,6 +52,17 @@ _helper_convert_error (const gchar *json_txt, GError **error)
+ }
+ json_root = json_parser_get_root (json_parser);
+ json_obj = json_node_get_object (json_root);
++ if (json_object_has_member (json_obj, "severity")) {
++ const gchar *severity;
++
++ /* warnings are non-fatal so we ignore them
++ */
++ severity = json_object_get_string_member (json_obj, "severity");
++ if (g_strstr_len (severity, -1, "warning") != NULL) {
++ return;
++ }
++ }
++
+ if (!json_object_has_member (json_obj, "message")) {
+ g_set_error (error,
+ G_IO_ERROR,
+@@ -108,6 +119,7 @@ static gboolean
+ _helper_auto_attach (GError **error)
+ {
+ const gchar *str = NULL;
++ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GDBusProxy) proxy = NULL;
+ g_autoptr(GVariantBuilder) proxy_options = NULL;
+ g_autoptr(GVariant) res = NULL;
+@@ -120,9 +132,12 @@ _helper_auto_attach (GError **error)
+ "com.redhat.RHSM1",
+ "/com/redhat/RHSM1/Attach",
+ "com.redhat.RHSM1.Attach",
+- NULL, error);
++ NULL, &error_local);
+ if (proxy == NULL) {
+- g_prefix_error (error, "Failed to get proxy: ");
++ g_dbus_error_strip_remote_error (error_local);
++ g_propagate_prefixed_error (error,
++ g_steal_pointer (&error_local),
++ "Failed to get proxy: ");
+ return FALSE;
+ }
+ proxy_options = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
+@@ -134,9 +149,18 @@ _helper_auto_attach (GError **error)
+ locale),
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_TIMEOUT,
+- NULL, error);
+- if (res == NULL)
+- return FALSE;
++ NULL, &error_local);
++ if (res == NULL) {
++ g_dbus_error_strip_remote_error (error_local);
++ _helper_convert_error (error_local->message, error);
++
++ if (*error != NULL) {
++ g_prefix_error (error, "Failed to get proxy: ");
++ return FALSE;
++ }
++
++ return TRUE;
++ }
+ g_variant_get (res, "(&s)", &str);
+ g_debug ("Attach.AutoAttach: %s", str);
+ return TRUE;
+@@ -327,8 +351,10 @@ main (int argc, char *argv[])
+ if (res == NULL) {
+ g_dbus_error_strip_remote_error (error_local);
+ _helper_convert_error (error_local->message, &error);
+- g_printerr ("Failed to RegisterWithActivationKeys: %s\n", error->message);
+- return error->code;
++ if (error != NULL) {
++ g_printerr ("Failed to RegisterWithActivationKeys: %s\n", error->message);
++ return error->code;
++ }
+ }
+ } else if (g_strcmp0 (kind, "register-with-username") == 0) {
+ g_autoptr(GError) error_local = NULL;
+@@ -370,8 +396,10 @@ main (int argc, char *argv[])
+ if (res == NULL) {
+ g_dbus_error_strip_remote_error (error_local);
+ _helper_convert_error (error_local->message, &error);
+- g_printerr ("Failed to Register: %s\n", error->message);
+- return error->code;
++ if (error != NULL) {
++ g_printerr ("Failed to Register: %s\n", error->message);
++ return error->code;
++ }
+ }
+ } else {
+ g_printerr ("Invalid --kind specified: %s\n", kind);
+--
+2.43.0
+
+
+From 087bfa30f5aae0233c181076d995bf06e4d10e3f Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 11:24:36 -0500
+Subject: [PATCH 10/21] subman: Add new no-installed-products state
+
+It's possible, though unlikley, the system has
+no packages installed from Red Hat supported package sets.
+
+This commit adds a new state to track that situation.
+---
+ plugins/subman/gsd-subman-common.c | 2 ++
+ plugins/subman/gsd-subman-common.h | 1 +
+ plugins/subman/gsd-subscription-manager.c | 17 +++++++----------
+ 3 files changed, 10 insertions(+), 10 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-common.c b/plugins/subman/gsd-subman-common.c
+index e515131e..eef5175d 100644
+--- a/plugins/subman/gsd-subman-common.c
++++ b/plugins/subman/gsd-subman-common.c
+@@ -32,5 +32,7 @@ gsd_subman_subscription_status_to_string (GsdSubmanSubscriptionStatus status)
+ return "disabled";
+ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID)
+ return "partially-valid";
++ if (status == GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS)
++ return "no-installed-products";
+ return "unknown";
+ }
+diff --git a/plugins/subman/gsd-subman-common.h b/plugins/subman/gsd-subman-common.h
+index fccf9f6a..f8a3d9f4 100644
+--- a/plugins/subman/gsd-subman-common.h
++++ b/plugins/subman/gsd-subman-common.h
+@@ -30,6 +30,7 @@ typedef enum {
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID,
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_LAST
+ } GsdSubmanSubscriptionStatus;
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 0838d490..46f8d35c 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -269,6 +269,13 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+
+ /* save old value */
+ priv->subscription_status_last = priv->subscription_status;
++ if (!_client_installed_products_update (manager, error))
++ goto out;
++
++ if (priv->installed_products->len == 0) {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS;
++ goto out;
++ }
+
+ uuid = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_CONSUMER],
+ "GetUuid",
+@@ -590,8 +597,6 @@ _client_register_with_keys (GsdSubscriptionManager *manager,
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
+- if (!_client_installed_products_update (manager, error))
+- return FALSE;
+ _client_maybe__show_notification (manager);
+
+ /* success */
+@@ -659,8 +664,6 @@ _client_register (GsdSubscriptionManager *manager,
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
+- if (!_client_installed_products_update (manager, error))
+- return FALSE;
+ _client_maybe__show_notification (manager);
+ return TRUE;
+ }
+@@ -741,10 +744,6 @@ _subman_proxy_signal_cb (GDBusProxy *proxy,
+ g_warning ("failed to update subscription status: %s", error->message);
+ g_clear_error (&error);
+ }
+- if (!_client_installed_products_update (manager, &error)) {
+- g_warning ("failed to update installed products: %s", error->message);
+- g_clear_error (&error);
+- }
+ _client_maybe__show_notification (manager);
+ }
+
+@@ -809,8 +808,6 @@ _client_load (GsdSubscriptionManager *manager, GError **error)
+ return FALSE;
+ if (!_client_subscription_status_update (manager, error))
+ return FALSE;
+- if (!_client_installed_products_update (manager, error))
+- return FALSE;
+ if (!_client_syspurpose_update (manager, error))
+ return FALSE;
+
+--
+2.43.0
+
+
+From 704908573d589bd90b9d5c705c97dfff52db0a7f Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 11:26:40 -0500
+Subject: [PATCH 11/21] subman: Fix some build warnings
+
+---
+ plugins/subman/gsd-subscription-manager.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 46f8d35c..1f9ca447 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -588,8 +588,8 @@ _client_register_with_keys (GsdSubscriptionManager *manager,
+
+ g_set_error (error, G_IO_ERROR, rc,
+ "%.*s",
+- g_bytes_get_size (stderr_buf),
+- g_bytes_get_data (stderr_buf, NULL));
++ (int) g_bytes_get_size (stderr_buf),
++ (char *) g_bytes_get_data (stderr_buf, NULL));
+ }
+
+ /* FIXME: also do on error? */
+@@ -655,8 +655,8 @@ _client_register (GsdSubscriptionManager *manager,
+
+ g_set_error (error, G_IO_ERROR, rc,
+ "%.*s",
+- g_bytes_get_size (stderr_buf),
+- g_bytes_get_data (stderr_buf, NULL));
++ (int) g_bytes_get_size (stderr_buf),
++ (char *) g_bytes_get_data (stderr_buf, NULL));
+ }
+
+ /* FIXME: also do on error? */
+--
+2.43.0
+
+
+From 4e08f49339698425f1c34bd82f484512cdc04f1e Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 11:27:42 -0500
+Subject: [PATCH 12/21] subman: Add DBus API to subscribe for updates on
+ already registered system
+
+It's possible an admin may have registered their system without
+attaching any subscriptions to it.
+
+At the moment, gnome-settings-daemon only provides a way to register
+and subscribe in one step.
+
+This commit adds an API to support doing the last half of the process
+on its own.
+---
+ plugins/subman/gsd-subscription-manager.c | 51 +++++++++++++++++++++++
+ 1 file changed, 51 insertions(+)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 1f9ca447..705f8b11 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -46,6 +46,7 @@ static const gchar introspection_xml[] =
+ " <arg type='a{sv}' name='options' direction='in'/>"
+ " </method>"
+ " <method name='Unregister'/>"
++" <method name='Attach'/>"
+ " <property name='InstalledProducts' type='aa{sv}' access='read'/>"
+ " <property name='SubscriptionStatus' type='u' access='read'/>"
+ " </interface>"
+@@ -696,6 +697,50 @@ _client_unregister (GsdSubscriptionManager *manager, GError **error)
+ return TRUE;
+ }
+
++static gboolean
++_client_attach (GsdSubscriptionManager *manager,
++ GError **error)
++{
++ g_autoptr(GSubprocess) subprocess = NULL;
++ g_autoptr(GBytes) stderr_buf = NULL;
++ gint rc;
++
++ g_debug ("spawning %s", LIBEXECDIR "/gsd-subman-helper");
++ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDERR_PIPE,
++ error,
++ "pkexec", LIBEXECDIR "/gsd-subman-helper",
++ "--kind", "auto-attach",
++ NULL);
++ if (subprocess == NULL) {
++ g_prefix_error (error, "failed to find pkexec: ");
++ return FALSE;
++ }
++
++ if (!g_subprocess_communicate (subprocess, NULL, NULL, NULL, &stderr_buf, error)) {
++ g_prefix_error (error, "failed to run pkexec: ");
++ return FALSE;
++ }
++
++ rc = g_subprocess_get_exit_status (subprocess);
++ if (rc != 0) {
++ if (g_bytes_get_size (stderr_buf) == 0) {
++ g_set_error_literal (error, G_IO_ERROR, rc,
++ "Failed to run helper without stderr");
++ return FALSE;
++ }
++
++ g_set_error (error, G_IO_ERROR, rc,
++ "%.*s",
++ (int) g_bytes_get_size (stderr_buf),
++ (char *) g_bytes_get_data (stderr_buf, NULL));
++ }
++
++ if (!_client_subscription_status_update (manager, error))
++ return FALSE;
++ _client_maybe__show_notification (manager);
++ return TRUE;
++}
++
+ static gboolean
+ _client_update_config (GsdSubscriptionManager *manager, GError **error)
+ {
+@@ -1029,6 +1074,12 @@ handle_method_call (GDBusConnection *connection,
+ return;
+ }
+ g_dbus_method_invocation_return_value (invocation, NULL);
++ } else if (g_strcmp0 (method_name, "Attach") == 0) {
++ if (!_client_attach (manager, &error)) {
++ g_dbus_method_invocation_return_gerror (invocation, error);
++ return;
++ }
++ g_dbus_method_invocation_return_value (invocation, NULL);
+ } else {
+ g_assert_not_reached ();
+ }
+--
+2.43.0
+
+
+From 7a98b24d40ba6a963043ed0d74f65e88937c6c55 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 11:34:03 -0500
+Subject: [PATCH 13/21] subman: Improve subscription status handling
+
+This commit improves how subscription-manager status is
+parsed to give more detailed information about subscription
+state.
+---
+ plugins/subman/gsd-subscription-manager.c | 33 +++++++++++++++++++----
+ 1 file changed, 28 insertions(+), 5 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 705f8b11..6d80bfa9 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -289,6 +289,11 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+
+ g_variant_get (uuid, "(&s)", &uuid_txt);
+
++ if (uuid_txt[0] == '\0') {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
++ goto out;
++ }
++
+ status = g_dbus_proxy_call_sync (priv->proxies[_RHSM_INTERFACE_ENTITLEMENT],
+ "GetStatus",
+ g_variant_new ("(ss)",
+@@ -304,6 +309,13 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+ return FALSE;
+ json_root = json_parser_get_root (json_parser);
+ json_obj = json_node_get_object (json_root);
++
++ const gchar *status_id = NULL;
++
++ if (json_object_has_member (json_obj, "status_id")) {
++ status_id = json_object_get_string_member (json_obj, "status_id");
++ }
++
+ if (!json_object_has_member (json_obj, "valid")) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ "no Entitlement.GetStatus valid in %s", json_txt);
+@@ -312,16 +324,27 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+
+ gboolean is_valid = json_object_get_boolean_member (json_obj, "valid");
+
+- if (uuid_txt[0] != '\0') {
+- if (is_valid) {
++ if (is_valid) {
++ if (g_strcmp0 (status_id, "disabled") != 0) {
+ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID;
+ } else {
+- priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED;
++ }
++ goto out;
++ }
++
++ for (guint i = 0; i < priv->installed_products->len; i++) {
++ ProductData *product = g_ptr_array_index (priv->installed_products, i);
++
++ if (g_strcmp0 (product->status, "subscribed") == 0) {
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID;
++ goto out;
+ }
+- } else {
+- priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN;
+ }
+
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID;
++
++out:
+ /* emit notification for g-c-c */
+ if (priv->subscription_status != priv->subscription_status_last) {
+ _emit_property_changed (manager, "SubscriptionStatus",
+--
+2.43.0
+
+
+From e725a1d6a6f8b67cee1cbee6215ffb3ec956c6d5 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 11:55:19 -0500
+Subject: [PATCH 14/21] subman: Drop "LAST" from status enum
+
+It's unused, so get rid of it.
+---
+ plugins/subman/gsd-subman-common.h | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/plugins/subman/gsd-subman-common.h b/plugins/subman/gsd-subman-common.h
+index f8a3d9f4..88226564 100644
+--- a/plugins/subman/gsd-subman-common.h
++++ b/plugins/subman/gsd-subman-common.h
+@@ -31,7 +31,6 @@ typedef enum {
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS,
+- GSD_SUBMAN_SUBSCRIPTION_STATUS_LAST
+ } GsdSubmanSubscriptionStatus;
+
+ const gchar *gsd_subman_subscription_status_to_string (GsdSubmanSubscriptionStatus status);
+--
+2.43.0
+
+
+From 2da1ab550a752aa431195103411d6f0b7ad4788a Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Sun, 24 Jan 2021 12:41:20 -0500
+Subject: [PATCH 15/21] subman: Clean up notification behavior
+
+Notifications were only displayed for some status transitions.
+
+This commit introduces some booleans based on the old and new
+statuses to make the code clearer and to make it easier to hit
+all the cases.
+---
+ plugins/subman/gsd-subman-common.h | 1 +
+ plugins/subman/gsd-subscription-manager.c | 141 ++++++++++++++++++----
+ 2 files changed, 120 insertions(+), 22 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-common.h b/plugins/subman/gsd-subman-common.h
+index 88226564..9397dbe4 100644
+--- a/plugins/subman/gsd-subman-common.h
++++ b/plugins/subman/gsd-subman-common.h
+@@ -25,6 +25,7 @@
+ G_BEGIN_DECLS
+
+ typedef enum {
++ GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ = -1,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID,
+ GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID,
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index 6d80bfa9..aaccbbc6 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -270,6 +270,7 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+
+ /* save old value */
+ priv->subscription_status_last = priv->subscription_status;
++
+ if (!_client_installed_products_update (manager, error))
+ goto out;
+
+@@ -512,55 +513,149 @@ static void
+ _client_maybe__show_notification (GsdSubscriptionManager *manager)
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ gboolean was_read, was_registered, had_subscriptions, needed_subscriptions;
++ gboolean is_read, is_registered, has_subscriptions, needs_subscriptions;
++
++ switch (priv->subscription_status_last) {
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ:
++ was_read = FALSE;
++ was_registered = FALSE;
++ needed_subscriptions = TRUE;
++ had_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN:
++ was_read = TRUE;
++ was_registered = FALSE;
++ needed_subscriptions = TRUE;
++ had_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID:
++ was_read = TRUE;
++ was_registered = TRUE;
++ needed_subscriptions = TRUE;
++ had_subscriptions = TRUE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID:
++ was_read = TRUE;
++ was_registered = TRUE;
++ needed_subscriptions = TRUE;
++ had_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED:
++ was_read = TRUE;
++ was_registered = TRUE;
++ needed_subscriptions = FALSE;
++ had_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID:
++ was_read = TRUE;
++ was_registered = TRUE;
++ needed_subscriptions = TRUE;
++ had_subscriptions = FALSE;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS:
++ was_read = TRUE;
++ was_registered = FALSE;
++ needed_subscriptions = FALSE;
++ had_subscriptions = FALSE;
++ break;
++ }
++
++ switch (priv->subscription_status) {
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ:
++ is_read = FALSE;
++ is_registered = FALSE;
++ needs_subscriptions = TRUE;
++ has_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN:
++ is_read = TRUE;
++ is_registered = FALSE;
++ needs_subscriptions = TRUE;
++ has_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID:
++ is_read = TRUE;
++ is_registered = TRUE;
++ needs_subscriptions = TRUE;
++ has_subscriptions = TRUE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID:
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID:
++ is_read = TRUE;
++ is_registered = TRUE;
++ needs_subscriptions = TRUE;
++ has_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_DISABLED:
++ is_read = TRUE;
++ is_registered = TRUE;
++ needs_subscriptions = FALSE;
++ has_subscriptions = FALSE;
++ break;
++ case GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS:
++ is_read = TRUE;
++ is_registered = FALSE;
++ needs_subscriptions = FALSE;
++ has_subscriptions = FALSE;
++ break;
++ }
+
+ /* startup */
+- if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
+- priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
++ if (!was_read && is_read && priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
+ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
+ return;
+ }
+
+ /* something changed */
+- if (priv->subscription_status_last != priv->subscription_status) {
++ if (was_read && is_read && priv->subscription_status_last != priv->subscription_status) {
+ g_debug ("transisition from subscription status '%s' to '%s'",
+ gsd_subman_subscription_status_to_string (priv->subscription_status_last),
+ gsd_subman_subscription_status_to_string (priv->subscription_status));
+
+- /* needs registration */
+- if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
+- priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID) {
+- _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
++ /* needs subscription */
++ if (is_registered && needs_subscriptions && !has_subscriptions) {
++ _show_notification (manager, _NOTIFY_EXPIRED);
+ return;
+ }
+
+ /* was unregistered */
+- if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
+- priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN) {
++ if (was_registered && !is_registered) {
+ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
+ return;
+ }
+
+- /* registered */
+- if (priv->subscription_status_last == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
+- priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_VALID &&
+- g_timer_elapsed (priv->timer_last_notified, NULL) > 60) {
+- _show_notification (manager, _NOTIFY_REGISTERED);
+- return;
++ /* just registered */
++ if (!was_registered && is_registered) {
++ if (!needs_subscriptions || has_subscriptions) {
++ _show_notification (manager, _NOTIFY_REGISTERED);
++ return;
++ }
++ }
++
++ /* subscriptions changed */
++ if (was_registered && is_registered) {
++ /* subscribed */
++ if (!had_subscriptions &&
++ needs_subscriptions && has_subscriptions) {
++ _show_notification (manager, _NOTIFY_REGISTERED);
++ return;
++ }
++
++ /* simple content access enabled */
++ if (needed_subscriptions && !had_subscriptions && !needs_subscriptions) {
++ _show_notification (manager, _NOTIFY_REGISTERED);
++ return;
++ }
+ }
+ }
+
+ /* nag again */
+- if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_UNKNOWN &&
++ if (!is_registered &&
+ g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
+ _show_notification (manager, _NOTIFY_REGISTRATION_REQUIRED);
+ return;
+ }
+- if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_INVALID &&
+- g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
+- _show_notification (manager, _NOTIFY_EXPIRED);
+- return;
+- }
+- if (priv->subscription_status == GSD_SUBMAN_SUBSCRIPTION_STATUS_PARTIALLY_VALID &&
++ if (is_registered && !has_subscriptions && needs_subscriptions &&
+ g_timer_elapsed (priv->timer_last_notified, NULL) > 60 * 60 * 24) {
+ _show_notification (manager, _NOTIFY_EXPIRED);
+ return;
+@@ -941,6 +1036,8 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+
+ priv->installed_products = g_ptr_array_new_with_free_func ((GDestroyNotify) product_data_free);
+ priv->timer_last_notified = g_timer_new ();
++ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ;
++ priv->subscription_status_last = GSD_SUBMAN_SUBSCRIPTION_STATUS_NOT_READ;
+
+ /* expired */
+ priv->notification_expired =
+--
+2.43.0
+
+
+From 98e2e95f43603f056d020f40b2bf461dcafba3a1 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Fri, 12 Feb 2021 14:51:29 +0100
+Subject: [PATCH 16/21] subman: Update POTFILES.in
+
+---
+ po/POTFILES.in | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index e721f526..7d5b7e9d 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -24,6 +24,8 @@ plugins/print-notifications/gsd-printer.c
+ plugins/print-notifications/gsd-print-notifications-manager.c
+ plugins/smartcard/gsd-smartcard-manager.c
+ plugins/smartcard/gsd-smartcard-service.c
++plugins/subman/gsd-subscription-manager.c
++plugins/subman/org.gnome.settings-daemon.plugins.subman.policy.in.in
+ plugins/usb-protection/gsd-usb-protection-manager.c
+ plugins/wacom/gsd-wacom-manager.c
+ plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in
+--
+2.43.0
+
+
+From 58e3919488aee5644516fe752b403630ebe03f80 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Mon, 6 Sep 2021 21:31:14 +0200
+Subject: [PATCH 17/21] subman: Don't force X11 backend
+
+All of this should work just fine with Wayland.
+---
+ plugins/subman/main.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/plugins/subman/main.c b/plugins/subman/main.c
+index 28ac995b..839c1b79 100644
+--- a/plugins/subman/main.c
++++ b/plugins/subman/main.c
+@@ -2,7 +2,6 @@
+ #define START gsd_subscription_manager_start
+ #define STOP gsd_subscription_manager_stop
+ #define MANAGER GsdSubscriptionManager
+-#define GDK_BACKEND "x11"
+ #include "gsd-subscription-manager.h"
+
+ #include "daemon-skeleton-gtk.h"
+--
+2.43.0
+
+
+From 2cf1112676922085a4b791a0235efaa721e64c97 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Tue, 7 Sep 2021 13:08:12 +0200
+Subject: [PATCH 18/21] subman: Fix desktop file hint for notifications
+
+We don't have a separate subman-panel. It's all part of
+info-overview-panel, as of now at least.
+---
+ plugins/subman/gsd-subscription-manager.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index aaccbbc6..be978fc3 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -1045,7 +1045,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ _("Add or renew a subscription to continue receiving software updates."),
+ NULL);
+ notify_notification_set_app_name (priv->notification_expired, _("Subscription"));
+- notify_notification_set_hint_string (priv->notification_expired, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_expired, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_expired, "x-gnome-privacy-scope", "system");
+ notify_notification_set_urgency (priv->notification_expired, NOTIFY_URGENCY_CRITICAL);
+ notify_notification_add_action (priv->notification_expired,
+@@ -1061,7 +1061,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ _("The system has been registered and software updates have been enabled."),
+ NULL);
+ notify_notification_set_app_name (priv->notification_registered, _("Subscription"));
+- notify_notification_set_hint_string (priv->notification_registered, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_registered, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_registered, "x-gnome-privacy-scope", "system");
+ notify_notification_set_urgency (priv->notification_registered, NOTIFY_URGENCY_CRITICAL);
+ g_signal_connect (priv->notification_registered, "closed",
+@@ -1073,7 +1073,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ _("Please register your system to receive software updates."),
+ NULL);
+ notify_notification_set_app_name (priv->notification_registration_required, _("Subscription"));
+- notify_notification_set_hint_string (priv->notification_registration_required, "desktop-entry", "subman-panel");
++ notify_notification_set_hint_string (priv->notification_registration_required, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_registration_required, "x-gnome-privacy-scope", "system");
+ notify_notification_set_urgency (priv->notification_registration_required, NOTIFY_URGENCY_CRITICAL);
+ notify_notification_add_action (priv->notification_registration_required,
+--
+2.43.0
+
+
+From 7fd3fe588aac6e37364b7d93596c8ea21a8f4ab6 Mon Sep 17 00:00:00 2001
+From: Kalev Lember <klember@redhat.com>
+Date: Wed, 8 Sep 2021 13:25:07 +0200
+Subject: [PATCH 19/21] subman: Use preferences-system icon for notifications
+
+Use it as a placeholder until we get a new icon for subscription
+management.
+---
+ plugins/subman/gsd-subscription-manager.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index be978fc3..dbb81098 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -1043,7 +1043,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ priv->notification_expired =
+ notify_notification_new (_("Subscription Has Expired"),
+ _("Add or renew a subscription to continue receiving software updates."),
+- NULL);
++ "preferences-system");
+ notify_notification_set_app_name (priv->notification_expired, _("Subscription"));
+ notify_notification_set_hint_string (priv->notification_expired, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_expired, "x-gnome-privacy-scope", "system");
+@@ -1059,7 +1059,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ priv->notification_registered =
+ notify_notification_new (_("Registration Successful"),
+ _("The system has been registered and software updates have been enabled."),
+- NULL);
++ "preferences-system");
+ notify_notification_set_app_name (priv->notification_registered, _("Subscription"));
+ notify_notification_set_hint_string (priv->notification_registered, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_registered, "x-gnome-privacy-scope", "system");
+@@ -1071,7 +1071,7 @@ gsd_subscription_manager_init (GsdSubscriptionManager *manager)
+ priv->notification_registration_required =
+ notify_notification_new (_("System Not Registered"),
+ _("Please register your system to receive software updates."),
+- NULL);
++ "preferences-system");
+ notify_notification_set_app_name (priv->notification_registration_required, _("Subscription"));
+ notify_notification_set_hint_string (priv->notification_registration_required, "desktop-entry", "gnome-info-overview-panel");
+ notify_notification_set_hint_string (priv->notification_registration_required, "x-gnome-privacy-scope", "system");
+--
+2.43.0
+
+
+From 87b4ef3f411f4df4d59850681d80f0be0a43be1e Mon Sep 17 00:00:00 2001
+From: Felipe Borges <felipeborges@gnome.org>
+Date: Fri, 15 Jul 2022 15:25:14 +0200
+Subject: [PATCH 20/21] subman: Set "enable_content" option only for Register
+
+The "enable_content" option doesn't work for
+RegisterWithActivationKey.
+---
+ plugins/subman/gsd-subman-helper.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/plugins/subman/gsd-subman-helper.c b/plugins/subman/gsd-subman-helper.c
+index 2152c521..04b599a4 100644
+--- a/plugins/subman/gsd-subman-helper.c
++++ b/plugins/subman/gsd-subman-helper.c
+@@ -299,8 +299,6 @@ main (int argc, char *argv[])
+ /* enable_content=1 auto attaches the subscription */
+ subman_options = g_variant_builder_new (G_VARIANT_TYPE("a{ss}"));
+
+- g_variant_builder_add (subman_options, "{ss}", "enable_content", "1");
+-
+ /* set registration server */
+ if (hostname == NULL || hostname[0] == '\0')
+ hostname = g_strdup ("subscription.rhsm.redhat.com");
+@@ -361,6 +359,8 @@ main (int argc, char *argv[])
+ g_autoptr(GVariant) res = NULL;
+ gchar password[PIPE_BUF + 1] = "";
+
++ g_variant_builder_add (subman_options, "{ss}", "enable_content", "1");
++
+ if (username == NULL) {
+ g_printerr ("Required --username\n");
+ return G_IO_ERROR_INVALID_DATA;
+--
+2.43.0
+
+
+From af1b217869f02788b3231530bf8368449180a5a7 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 4 Jan 2024 10:09:33 -0500
+Subject: [PATCH 21/21] subman: Make subscription-manager a soft dependency
+
+Some RHEL customers don't rely on subscription-manager for updates,
+but instead push updates via imaging. That is particularly important
+for "air gapped" installations that have no network access during
+normal operation.
+
+This commit makes the subman plugin more forgiving of situations where
+subscription-manager isn't installed.
+---
+ plugins/subman/gsd-subscription-manager.c | 118 ++++++++++++++++++----
+ 1 file changed, 96 insertions(+), 22 deletions(-)
+
+diff --git a/plugins/subman/gsd-subscription-manager.c b/plugins/subman/gsd-subscription-manager.c
+index dbb81098..09a96a3a 100644
+--- a/plugins/subman/gsd-subscription-manager.c
++++ b/plugins/subman/gsd-subscription-manager.c
+@@ -271,8 +271,9 @@ _client_subscription_status_update (GsdSubscriptionManager *manager, GError **er
+ /* save old value */
+ priv->subscription_status_last = priv->subscription_status;
+
+- if (!_client_installed_products_update (manager, error))
+- goto out;
++ if (!_client_installed_products_update (manager, error)) {
++ return FALSE;
++ }
+
+ if (priv->installed_products->len == 0) {
+ priv->subscription_status = GSD_SUBMAN_SUBSCRIPTION_STATUS_NO_INSTALLED_PRODUCTS;
+@@ -939,40 +940,109 @@ _rhsm_interface_to_string (_RhsmInterface kind)
+ return NULL;
+ }
+
++static void
++finish_loading (GsdSubscriptionManager *manager)
++{
++ g_autoptr (GError) error = NULL;
++
++ if (!_client_update_config (manager, &error)) {
++ g_debug ("Could not read subscription manager config: %s",
++ error->message);
++ return;
++ }
++
++ if (!_client_subscription_status_update (manager, &error)) {
++ g_debug ("Could not read subscription manager status: %s",
++ error->message);
++ return;
++ }
++
++ if (!_client_syspurpose_update (manager, &error)) {
++ g_debug ("Could not read system purpose from subscription manager: %s",
++ error->message);
++ return;
++ }
++
++ _client_maybe__show_notification (manager);
++}
++
++static void
++on_subscription_manager_available (GsdSubscriptionManager *manager)
++{
++ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autofree char *name_owner = NULL;
++
++ name_owner = g_dbus_proxy_get_name_owner (priv->proxies[0]);
++
++ if (name_owner == NULL) {
++ notify_notification_close (priv->notification_registered, NULL);
++ notify_notification_close (priv->notification_registration_required, NULL);
++ notify_notification_close (priv->notification_expired, NULL);
++ return;
++ }
++
++ finish_loading (manager);
++}
++
+ static gboolean
+-_client_load (GsdSubscriptionManager *manager, GError **error)
++start_loading (GsdSubscriptionManager *manager, GError **error)
+ {
+ GsdSubscriptionManagerPrivate *priv = manager->priv;
++ g_autoptr(GDBusConnection) connection = NULL;
++ g_autofree char *name_owner = NULL;
+
+ priv->config = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
++ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
++
++ if (connection == NULL) {
++ return FALSE;
++ }
++
++ g_dbus_connection_call (connection,
++ "org.freedesktop.DBus",
++ "/org/freedesktop/DBus",
++ "org.freedesktop.DBus",
++ "StartServiceByName",
++ g_variant_new("(su)", "com.redhat.RHSM1", 0),
++ NULL,
++ G_DBUS_CALL_FLAGS_NONE,
++ -1,
++ NULL,
++ NULL,
++ NULL);
++
+ /* connect to all the interfaces on the *different* objects :| */
+ for (guint i = 0; i < _RHSM_INTERFACE_LAST; i++) {
+ const gchar *kind = _rhsm_interface_to_string (i);
+ g_autofree gchar *opath = g_strdup_printf ("/com/redhat/RHSM1/%s", kind);
+ g_autofree gchar *iface = g_strdup_printf ("com.redhat.RHSM1.%s", kind);
+ priv->proxies[i] =
+- g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+- G_DBUS_PROXY_FLAGS_NONE,
+- NULL,
+- "com.redhat.RHSM1",
+- opath, iface,
+- NULL,
+- error);
++ g_dbus_proxy_new_sync (connection,
++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
++ NULL,
++ "com.redhat.RHSM1",
++ opath, iface,
++ NULL,
++ error);
+ if (priv->proxies[i] == NULL)
+ return FALSE;
+ /* we want to get notified if the status of the system changes */
+ g_signal_connect (priv->proxies[i], "g-signal",
+ G_CALLBACK (_subman_proxy_signal_cb), manager);
++
+ }
+
+- /* get initial status */
+- if (!_client_update_config (manager, error))
+- return FALSE;
+- if (!_client_subscription_status_update (manager, error))
+- return FALSE;
+- if (!_client_syspurpose_update (manager, error))
+- return FALSE;
++ g_signal_connect_object (G_OBJECT (priv->proxies[0]),
++ "notify::g-name-owner",
++ G_CALLBACK (on_subscription_manager_available),
++ manager,
++ G_CONNECT_SWAPPED);
++
++ name_owner = g_dbus_proxy_get_name_owner (priv->proxies[0]);
++ if (name_owner != NULL) {
++ finish_loading (manager);
++ }
+
+ /* success */
+ return TRUE;
+@@ -981,13 +1051,16 @@ _client_load (GsdSubscriptionManager *manager, GError **error)
+ gboolean
+ gsd_subscription_manager_start (GsdSubscriptionManager *manager, GError **error)
+ {
+- gboolean ret;
++ g_autoptr (GError) local_error = NULL;
++
+ g_debug ("Starting subscription manager");
+ gnome_settings_profile_start (NULL);
+- ret = _client_load (manager, error);
+- _client_maybe__show_notification (manager);
++ if (!start_loading (manager, &local_error)) {
++ g_debug ("Could not start subscription manager: %s",
++ local_error->message);
++ }
+ gnome_settings_profile_end (NULL);
+- return ret;
++ return TRUE;
+ }
+
+ void
+@@ -1222,8 +1295,9 @@ handle_get_property (GDBusConnection *connection,
+ return NULL;
+ }
+
+- if (g_strcmp0 (property_name, "SubscriptionStatus") == 0)
++ if (g_strcmp0 (property_name, "SubscriptionStatus") == 0) {
+ return g_variant_new_uint32 (priv->subscription_status);
++ }
+
+ if (g_strcmp0 (property_name, "InstalledProducts") == 0)
+ return _make_installed_products_variant (priv->installed_products);
+--
+2.43.0
+