summaryrefslogtreecommitdiff
path: root/kerberos-fixes.patch
diff options
context:
space:
mode:
Diffstat (limited to 'kerberos-fixes.patch')
-rw-r--r--kerberos-fixes.patch5242
1 files changed, 5242 insertions, 0 deletions
diff --git a/kerberos-fixes.patch b/kerberos-fixes.patch
new file mode 100644
index 0000000..25c03a0
--- /dev/null
+++ b/kerberos-fixes.patch
@@ -0,0 +1,5242 @@
+From eb41a7c7c237797d2902d2c7b05f9d2d46fac070 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir@gnome.org>
+Date: Wed, 12 Oct 2022 22:59:34 +0200
+Subject: [PATCH 01/22] kerberos-identity-manager: Clarify an ambiguous debug
+ log about KCM
+
+Kerberos KCM credential caches do support multiple identities and a lot
+of users use KCM these days because it's the default on Fedora. Hence,
+it's better not to have a debug log that implies that the code wasn't
+expecting KCM and is making assumptions about it - KCM is definitely a
+supported Kerberos cache type.
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/merge_requests/104
+---
+ src/goaidentity/goakerberosidentitymanager.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index c35aa8b6..d4ff2de4 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -806,61 +806,63 @@ get_identity (GoaKerberosIdentityManager *self,
+ GoaIdentity *identity;
+
+ g_debug ("GoaKerberosIdentityManager: get identity %s", operation->identifier);
+ identity = g_hash_table_lookup (self->identities, operation->identifier);
+
+ if (identity == NULL)
+ {
+ g_task_return_new_error (operation->task,
+ GOA_IDENTITY_MANAGER_ERROR,
+ GOA_IDENTITY_MANAGER_ERROR_IDENTITY_NOT_FOUND,
+ _("Could not find identity"));
+ return;
+ }
+
+ g_task_return_pointer (operation->task, g_object_ref (identity), g_object_unref);
+ }
+
+ static krb5_error_code
+ get_new_credentials_cache (GoaKerberosIdentityManager *self,
+ krb5_ccache *credentials_cache)
+ {
+ krb5_error_code error_code;
+ gboolean supports_multiple_identities;
+
+ if (g_strcmp0 (self->credentials_cache_type, "FILE") == 0)
+ {
+ g_debug ("GoaKerberosIdentityManager: credential cache type %s doesn't supports cache collections",
+ self->credentials_cache_type);
+ supports_multiple_identities = FALSE;
+ }
+- else if (g_strcmp0 (self->credentials_cache_type, "DIR") == 0 || g_strcmp0 (self->credentials_cache_type, "KEYRING") == 0)
++ else if (g_strcmp0 (self->credentials_cache_type, "DIR") == 0
++ || g_strcmp0 (self->credentials_cache_type, "KCM") == 0
++ || g_strcmp0 (self->credentials_cache_type, "KEYRING") == 0)
+ {
+ g_debug ("GoaKerberosIdentityManager: credential cache type %s supports cache collections", self->credentials_cache_type);
+ supports_multiple_identities = TRUE;
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentityManager: don't know if credential cache type %s supports cache collections, "
+ "assuming yes",
+ self->credentials_cache_type);
+ supports_multiple_identities = TRUE;
+ }
+
+ /* If we're configured for FILE based credentials, then we only
+ * have one ccache, and we need to use it always.
+ *
+ * If we're configured for DIR or KEYRING based credentials, then we
+ * can have multiple ccache's so we should use the default one first
+ * (so it gets selected automatically) and then fallback to unique
+ * ccache names for subsequent tickets.
+ *
+ */
+ if (!supports_multiple_identities || g_hash_table_size (self->identities) == 0)
+ error_code = krb5_cc_default (self->kerberos_context, credentials_cache);
+ else
+ error_code = krb5_cc_new_unique (self->kerberos_context, self->credentials_cache_type, NULL, credentials_cache);
+
+ return error_code;
+ }
+
+ static void
+--
+2.39.3
+
+
+From 3bd1d5df6a25899651d2af72a5c226c3696e9f7c Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir@gnome.org>
+Date: Thu, 13 Oct 2022 18:46:46 +0200
+Subject: [PATCH 02/22] kerberos-identity: Clarify and remove redundancy from
+ the renewal errors
+
+The "Could not renew identity" segment in the error strings from
+goa_kerberos_identity_renew is redundant because the caller already
+knows that this function is about renewing an identity and any failure
+means just that. In fact, it's callers in GoaIdentityService and
+GoaKerberosIdentityManager already prepend a similar segment when using
+the error strings.
+
+Debug logs are already verbose enough. It's better not to make it even
+more difficult to follow them through needless redundancy.
+
+When reporting errors from krb5_cc_get_principal, it's good to make the
+error string match the documentation [1] of that function because it
+makes the code self-documenting.
+
+[1] https://web.mit.edu/kerberos/krb5-devel/doc/appdev/refs/api/krb5_cc_get_principal.html
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 45d54f4d..3d2fe25c 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1403,71 +1403,72 @@ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->credentials_cache == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+- _("Could not renew identity: Not signed in"));
++ _("Not signed in"));
+ goto out;
+ }
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+- error_code, _("Could not renew identity: "));
++ error_code,
++ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+ _("Could not get new credentials to renew identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+--
+2.39.3
+
+
+From a2e8e45b8e44201b9704ee31b9d762f71379be95 Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir@gnome.org>
+Date: Thu, 13 Oct 2022 19:02:59 +0200
+Subject: [PATCH 03/22] kerberos-identity: Clarify the error when talking to
+ the KDC failed
+
+When krb5_get_renewed_creds fails to talk to the Kerberos Key
+Distribution Centre (or KDC) because of network problems,
+krb5_get_error_message translates the krb5_error_code as:
+ Resource temporarily unavailable
+
+This ends up in the debug logs as:
+ GoaKerberosIdentityManager: could not renew identity: Could not get
+ new credentials to renew identity FOO@BAR.ORG: Resource temporarily
+ unavailable
+ GoaIdentityService: could not renew identity: Could not get new
+ credentials to renew identity FOO@BAR.ORG: Resource temporarily
+ unavailable
+
+It's not immediately clear that the 'resource' that's 'temporarily
+unavailable' is actually the KDC, which would make it easier to
+understand that there is a network problem.
+
+Therefore, mention KDC in the error string from krb5_get_renewed_creds
+and make it match the documentation [1] of that function because it
+makes the code self-documenting.
+
+[1] https://web.mit.edu/kerberos/krb5-devel/doc/appdev/refs/api/krb5_get_renewed_creds.html
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 3d2fe25c..57ab616f 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1427,61 +1427,61 @@ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+
+ if (self->credentials_cache == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+- _("Could not get new credentials to renew identity %s: "),
++ _("Could not get renewed credentials from the KDC for identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+
+ if (self->credentials_cache != NULL)
+--
+2.39.3
+
+
+From 4b4719ea24e8a8503ccd015659eb82e0fbdbb7de Mon Sep 17 00:00:00 2001
+From: Debarshi Ray <debarshir@gnome.org>
+Date: Thu, 13 Oct 2022 22:14:07 +0200
+Subject: [PATCH 04/22] kerberos-identity: Fail initialization if an identifier
+ can't be found
+
+The inability to get an identifier already leads to an error.
+Continuing beyond that point can lead to the verification_error trying
+to clobber it.
+
+https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/merge_requests/107
+---
+ src/goaidentity/goakerberosidentity.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 57ab616f..b72ce6ab 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -958,63 +958,64 @@ reset_alarms (GoaKerberosIdentity *self)
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (self->identifier == NULL)
+ {
+ self->identifier = get_identifier (self, error);
++ if (self->identifier == NULL)
++ return FALSE;
+
+- if (self->identifier != NULL)
+- queue_notify (self, &self->identifier_idle_id, "identifier");
++ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+
+ verification_error = NULL;
+ self->cached_verification_level = verify_identity (self, &self->preauth_identity_source, &verification_error);
+
+ switch (self->cached_verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ reset_alarms (self);
+
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ return TRUE;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ return TRUE;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ if (verification_error != NULL)
+ {
+ g_propagate_error (error, verification_error);
+ return FALSE;
+ }
+
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_VERIFYING,
+ _("No associated identification found"));
+ return FALSE;
+--
+2.39.3
+
+
+From 10da18e2f5ab562ce7c0ea52701956adb80a118f Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 27 Oct 2022 09:18:53 -0400
+Subject: [PATCH 05/22] kerberos-identity: Ensure idles queued to main thread
+ are property synchronized
+
+Kerberos identities are refreshed on a helper thread, and the state of
+those identities are exported over the user bus on the main thread.
+
+Since the main consumer of an identity's properties is the bus service
+running on the main thread, to simplify things, property notifications
+are dispatched from the main thread as well (even though the underlying
+state is changed on a worker thread).
+
+The mechanism to dispatch property notifies to the main thread is an
+idle handler. The logic for doing the dispatch has a concurrency
+bug however. In order to coaelsce multiple notifies that happen in
+quick succession, the dispatch code checks for a preexisting idle id
+associated with the given property. That idle id is set from the worker
+thread when the idle is queued, and it's cleared from the main thread
+when the idle is dispatched. The bug is that the main thread could in
+theory clear the idle id immediately after the worker thread decided
+there was already a notify queued, leading to a notify getting
+completely dropped.
+
+This commit addresses the bug by adding appropriate locking.
+
+Closes https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/160
+---
+ src/goaidentity/goakerberosidentity.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b72ce6ab..695396bf 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -493,61 +493,64 @@ snoop_preauth_identity_from_credentials (GoaKerberosIdentity *self,
+ static krb5_timestamp
+ get_current_time (GoaKerberosIdentity *self)
+ {
+ krb5_timestamp current_time;
+ krb5_error_code error_code;
+
+ error_code = krb5_timeofday (self->kerberos_context, &current_time);
+ if (error_code != 0)
+ {
+ const char *error_message;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentity: Error getting current time: %s", error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ return 0;
+ }
+
+ return current_time;
+ }
+
+ typedef struct
+ {
+ GoaKerberosIdentity *self;
+ guint *idle_id;
+ const char *property_name;
+ } NotifyRequest;
+
+ static void
+ clear_idle_id (NotifyRequest *request)
+ {
++ G_LOCK (identity_lock);
+ *request->idle_id = 0;
++ G_UNLOCK (identity_lock);
++
+ g_object_unref (request->self);
+ g_slice_free (NotifyRequest, request);
+ }
+
+ static gboolean
+ on_notify_queued (NotifyRequest *request)
+ {
+ g_object_notify (G_OBJECT (request->self), request->property_name);
+
+ return FALSE;
+ }
+
+ static void
+ queue_notify (GoaKerberosIdentity *self,
+ guint *idle_id,
+ const char *property_name)
+ {
+ NotifyRequest *request;
+
+ if (*idle_id != 0)
+ {
+ return;
+ }
+
+ request = g_slice_new0 (NotifyRequest);
+ request->self = g_object_ref (self);
+ request->idle_id = idle_id;
+ request->property_name = property_name;
+
+ *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+--
+2.39.3
+
+
+From b8cb0f6df2742e0a79f764edf8f99ffa40a4347d Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 13 Oct 2022 16:11:54 -0400
+Subject: [PATCH 06/22] identity: Don't add temporary accounts for expired
+ credentials
+
+The identity service creates a "temporary" kerberos account when
+a user manually does kinit, to handle automatic renewal, etc.
+
+Unfortunately, it also picks up expired cruft that builds up in
+KCM based credential caches, and creates temporary accounts for
+that as well.
+
+This commit tries to avoid that.
+
+Closes https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/32
+---
+ src/goaidentity/goaidentityservice.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goaidentityservice.c b/src/goaidentity/goaidentityservice.c
+index 3dd27060..a25de416 100644
+--- a/src/goaidentity/goaidentityservice.c
++++ b/src/goaidentity/goaidentityservice.c
+@@ -1070,61 +1070,61 @@ add_temporary_account (GoaIdentityService *self,
+ g_object_ref (identity));
+ }
+ else
+ {
+ add_temporary_account_as_kerberos (self,
+ identity,
+ NULL,
+ on_temporary_account_added_as_kerberos,
+ g_object_ref (identity));
+ }
+
+ g_free (realm);
+ }
+
+ /* ---------------------------------------------------------------------------------------------------- */
+
+ static void
+ on_identity_added (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+
+ export_identity (self, identity);
+
+ identifier = goa_identity_get_identifier (identity);
+
+ object = find_object_with_principal (self, identifier, FALSE);
+
+- if (object == NULL)
++ if (object == NULL && goa_identity_is_signed_in (identity))
+ add_temporary_account (self, identity);
+
+ g_clear_object (&object);
+ }
+
+ static void
+ on_identity_removed (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+
+ identifier = goa_identity_get_identifier (identity);
+ object = find_object_with_principal (self, identifier, FALSE);
+
+ if (object != NULL)
+ ensure_account_credentials (self, object);
+
+ unexport_identity (self, identity);
+ g_clear_object (&object);
+ }
+
+ static void
+ on_identity_refreshed (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ GoaObject *object;
+ const char *identifier;
+--
+2.39.3
+
+
+From bf61b7ed9842be060d50c7ef0fa6f5987d05d67b Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 28 Nov 2022 14:16:09 -0500
+Subject: [PATCH 07/22] kerberos-identity: Attempt to cope with multiple
+ credential caches per identity
+
+At the moment the identity service assumes there will just be one
+credential cache collection for any given prinicipal.
+
+This isn't necessarily true though, and the service gets quite
+confused when that assumption doesn't hold up.
+
+This commit attempts to make it cope with the situation better, by
+maintaining a hash table of collections per identity. It deems
+one of the collections the "active" one and relegates the rest to
+be backup if the active one expires and can't be renewed.
+
+Closes: https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/issues/79
+---
+ src/goaidentity/goakerberosidentity.c | 340 ++++++++++++++++++++++----
+ 1 file changed, 287 insertions(+), 53 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 695396bf..dbb5991d 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -6,73 +6,76 @@
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+ #include "config.h"
+
+ #include "goaidentity.h"
+ #include "goakerberosidentity.h"
+ #include "goakerberosidentityinquiry.h"
+ #include "goaalarm.h"
+
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+
+ #include <string.h>
+ #include <glib/gi18n.h>
+ #include <gio/gio.h>
+
+ typedef enum
+ {
++ VERIFICATION_LEVEL_ERROR = -1,
+ VERIFICATION_LEVEL_UNVERIFIED,
+- VERIFICATION_LEVEL_ERROR,
+ VERIFICATION_LEVEL_EXISTS,
+ VERIFICATION_LEVEL_SIGNED_IN
+ } VerificationLevel;
+
+ struct _GoaKerberosIdentity
+ {
+ GObject parent;
+
+ krb5_context kerberos_context;
+ krb5_ccache credentials_cache;
+
++ GHashTable *credentials_caches;
++ char *active_credentials_cache_name;
++
+ char *identifier;
+ guint identifier_idle_id;
+
+ char *preauth_identity_source;
+
+ krb5_timestamp start_time;
+ guint start_time_idle_id;
+ krb5_timestamp renewal_time;
+ guint renewal_time_idle_id;
+ krb5_timestamp expiration_time;
+ guint expiration_time_idle_id;
+
+ GoaAlarm *expiration_alarm;
+ GoaAlarm *expiring_alarm;
+ GoaAlarm *renewal_alarm;
+
+ VerificationLevel cached_verification_level;
+ guint is_signed_in_idle_id;
+ };
+
+ enum
+ {
+ EXPIRING,
+ EXPIRED,
+ UNEXPIRED,
+ NEEDS_RENEWAL,
+ NEEDS_REFRESH,
+ NUMBER_OF_SIGNALS,
+ };
+
+@@ -82,84 +85,99 @@ enum
+ PROP_IDENTIFIER,
+ PROP_IS_SIGNED_IN,
+ PROP_START_TIMESTAMP,
+ PROP_RENEWAL_TIMESTAMP,
+ PROP_EXPIRATION_TIMESTAMP
+ };
+
+ static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+ static void identity_interface_init (GoaIdentityInterface *interface);
+ static void initable_interface_init (GInitableIface *interface);
+ static void reset_alarms (GoaKerberosIdentity *self);
+ static void clear_alarms (GoaKerberosIdentity *self);
+ static gboolean goa_kerberos_identity_is_signed_in (GoaIdentity *identity);
+ static void set_and_prefix_error_from_krb5_error_code (GoaKerberosIdentity *self,
+ GError **error,
+ gint code,
+ krb5_error_code error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+ G_LOCK_DEFINE_STATIC (identity_lock);
+
+ G_DEFINE_TYPE_WITH_CODE (GoaKerberosIdentity,
+ goa_kerberos_identity,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ initable_interface_init)
+ G_IMPLEMENT_INTERFACE (GOA_TYPE_IDENTITY,
+ identity_interface_init));
++
++static void
++close_credentials_caches (GoaKerberosIdentity *self)
++{
++ GHashTableIter iter;
++ const char *name;
++ krb5_ccache credentials_cache;
++
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ }
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++}
++
+ static void
+ goa_kerberos_identity_dispose (GObject *object)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ G_LOCK (identity_lock);
+ clear_alarms (self);
+ g_clear_pointer (&self->preauth_identity_source, g_free);
++ close_credentials_caches (self);
++ g_clear_pointer (&self->credentials_caches, g_hash_table_unref);
+ G_UNLOCK (identity_lock);
+
+ G_OBJECT_CLASS (goa_kerberos_identity_parent_class)->dispose (object);
+
+ }
+
+ static void
+ goa_kerberos_identity_finalize (GObject *object)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ g_free (self->identifier);
+
+- if (self->credentials_cache != NULL)
+- krb5_cc_close (self->kerberos_context, self->credentials_cache);
+-
+ G_OBJECT_CLASS (goa_kerberos_identity_parent_class)->finalize (object);
+ }
+
+ static void
+ goa_kerberos_identity_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *param_spec)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (object);
+
+ switch (property_id)
+ {
+ case PROP_IDENTIFIER:
+ G_LOCK (identity_lock);
+ g_value_set_string (value, self->identifier);
+ G_UNLOCK (identity_lock);
+ break;
+ case PROP_IS_SIGNED_IN:
+ g_value_set_boolean (value,
+ goa_kerberos_identity_is_signed_in (GOA_IDENTITY (self)));
+ break;
+ case PROP_START_TIMESTAMP:
+ G_LOCK (identity_lock);
+ g_value_set_int64 (value, (gint64) self->start_time);
+ G_UNLOCK (identity_lock);
+ break;
+ case PROP_RENEWAL_TIMESTAMP:
+ G_LOCK (identity_lock);
+ g_value_set_int64 (value, (gint64) self->renewal_time);
+@@ -228,109 +246,114 @@ goa_kerberos_identity_class_init (GoaKerberosIdentityClass *klass)
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_override_property (object_class, PROP_IDENTIFIER, "identifier");
+ g_object_class_override_property (object_class, PROP_IS_SIGNED_IN, "is-signed-in");
+ g_object_class_override_property (object_class,
+ PROP_START_TIMESTAMP,
+ "start-timestamp");
+ g_object_class_override_property (object_class,
+ PROP_RENEWAL_TIMESTAMP,
+ "renewal-timestamp");
+ g_object_class_override_property (object_class,
+ PROP_EXPIRATION_TIMESTAMP,
+ "expiration-timestamp");
+
+ }
+
+ static char *
+ get_identifier (GoaKerberosIdentity *self,
+ GError **error)
+ {
+ krb5_principal principal;
+ krb5_error_code error_code;
+ char *unparsed_name;
+ char *identifier = NULL;
++ krb5_ccache credentials_cache;
+
+- if (self->credentials_cache == NULL)
++ if (self->active_credentials_cache_name == NULL)
+ return NULL;
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ if (error_code == KRB5_CC_END)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ }
+ else
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ }
+ return NULL;
+ }
+
+ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_name);
+ if (error_code != 0)
+ {
+ const char *error_message;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentity: Error parsing principal identity name: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ goto out;
+ }
+
+ identifier = g_strdup (unparsed_name);
+ krb5_free_unparsed_name (self->kerberos_context, unparsed_name);
+
+ out:
+ krb5_free_principal (self->kerberos_context, principal);
+ return identifier;
+ }
+
+ static void
+ goa_kerberos_identity_init (GoaKerberosIdentity *self)
+ {
++ self->credentials_caches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ static void
+ set_and_prefix_error_from_krb5_error_code (GoaKerberosIdentity *self,
+ GError **error,
+ gint code,
+ krb5_error_code error_code,
+ const char *format,
+ ...)
+ {
+ const char *error_message;
+ char *literal_prefix;
+ va_list args;
+
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_set_error_literal (error, GOA_IDENTITY_ERROR, code, error_message);
+
+ va_start (args, format);
+ literal_prefix = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_prefix_error (error, "%s", literal_prefix);
+
+ g_free (literal_prefix);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ }
+
+ char *
+ goa_kerberos_identity_get_principal_name (GoaKerberosIdentity *self)
+ {
+@@ -613,169 +636,280 @@ examine_credentials (GoaKerberosIdentity *self,
+ krb5_timestamp current_time;
+
+ G_LOCK (identity_lock);
+
+ if (credentials->times.starttime != 0)
+ credentials_start_time = credentials->times.starttime;
+ else
+ credentials_start_time = credentials->times.authtime;
+
+ *renewal_time = credentials->times.renew_till;
+
+ credentials_end_time = credentials->times.endtime;
+
+ if (self->start_time == 0)
+ *start_time = credentials_start_time;
+ else
+ *start_time = MIN (self->start_time, credentials_start_time);
+ *expiration_time = MAX (credentials->times.endtime, self->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ current_time = get_current_time (self);
+
+ if (current_time < credentials_start_time ||
+ credentials_end_time <= current_time)
+ *are_expired = TRUE;
+ else
+ *are_expired = FALSE;
+ }
+
+ static VerificationLevel
+-verify_identity (GoaKerberosIdentity *self,
+- char **preauth_identity_source,
+- GError **error)
++verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
++ char **preauth_identity_source,
++ krb5_ccache credentials_cache,
++ krb5_timestamp *start_time,
++ krb5_timestamp *renewal_time,
++ krb5_timestamp *expiration_time,
++ GError **error)
+ {
+ krb5_principal principal = NULL;
+ krb5_cc_cursor cursor;
+ krb5_creds credentials;
+ krb5_error_code error_code;
+- krb5_timestamp start_time = 0;
+- krb5_timestamp renewal_time = 0;
+- krb5_timestamp expiration_time = 0;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+- if (self->credentials_cache == NULL)
+- goto out;
++ g_debug ("GoaKerberosIdentity: Verifying identity in credentials cache '%s'",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
++ if (error_code == KRB5_CC_END)
++ g_debug ("GoaKerberosIdentity: Credentials cache empty");
++ else if (error_code == KRB5_FCC_NOFILE)
++ g_debug ("GoaKerberosIdentity: Credentials cache missing");
++
+ if (error_code == KRB5_CC_END || error_code == KRB5_FCC_NOFILE)
+ goto out;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_NOT_FOUND,
+ error_code,
+ _("Could not find identity in credential cache: "));
+ verification_level = VERIFICATION_LEVEL_ERROR;
+ goto out;
+ }
+
+- error_code = krb5_cc_start_seq_get (self->kerberos_context, self->credentials_cache, &cursor);
++ error_code = krb5_cc_start_seq_get (self->kerberos_context, credentials_cache, &cursor);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not find identity credentials in cache: "));
+
+ verification_level = VERIFICATION_LEVEL_ERROR;
+ goto out;
+ }
+
+ verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+- error_code = krb5_cc_next_cred (self->kerberos_context, self->credentials_cache, &cursor, &credentials);
++ error_code = krb5_cc_next_cred (self->kerberos_context, credentials_cache, &cursor, &credentials);
+ while (error_code == 0)
+ {
+ if (credentials_validate_existence (self, principal, &credentials))
+ {
+ gboolean credentials_are_expired = TRUE;
+
+- examine_credentials (self, &credentials,
+- &start_time,
+- &renewal_time,
+- &expiration_time,
++ examine_credentials (self,
++ &credentials,
++ start_time,
++ renewal_time,
++ expiration_time,
+ &credentials_are_expired);
+
+ if (!credentials_are_expired)
+ verification_level = VERIFICATION_LEVEL_SIGNED_IN;
+ else
+ verification_level = VERIFICATION_LEVEL_EXISTS;
+ }
+ else
+ {
+ snoop_preauth_identity_from_credentials (self, &credentials, preauth_identity_source);
+ }
+
+ krb5_free_cred_contents (self->kerberos_context, &credentials);
+- error_code = krb5_cc_next_cred (self->kerberos_context, self->credentials_cache, &cursor, &credentials);
++ error_code = krb5_cc_next_cred (self->kerberos_context, credentials_cache, &cursor, &credentials);
+ }
+
+ if (error_code != KRB5_CC_END)
+ {
+ verification_level = VERIFICATION_LEVEL_ERROR;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not sift through identity credentials in cache: "));
+- goto end_sequence;
+ }
+
+- end_sequence:
+- error_code = krb5_cc_end_seq_get (self->kerberos_context, self->credentials_cache, &cursor);
++ error_code = krb5_cc_end_seq_get (self->kerberos_context, credentials_cache, &cursor);
+ if (error_code != 0)
+ {
+ verification_level = VERIFICATION_LEVEL_ERROR;
+
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ENUMERATING_CREDENTIALS,
+ error_code,
+ _("Could not finish up sifting through "
+ "identity credentials in cache: "));
+ goto out;
+ }
++
+ out:
++ switch (verification_level)
++ {
++ case VERIFICATION_LEVEL_EXISTS:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are out of date",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_SIGNED_IN:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are valid",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_UNVERIFIED:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are missing",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++
++ case VERIFICATION_LEVEL_ERROR:
++ default:
++ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' could not be validated",
++ krb5_cc_get_name (self->kerberos_context, credentials_cache));
++ break;
++ }
++
++ if (principal != NULL)
++ krb5_free_principal (self->kerberos_context, principal);
++ return verification_level;
++}
++
++static VerificationLevel
++verify_identity (GoaKerberosIdentity *self,
++ char **preauth_identity_source,
++ GError **error)
++{
++ krb5_ccache credentials_cache;
++ const char *name;
++ krb5_timestamp start_time = 0;
++ krb5_timestamp renewal_time = 0;
++ krb5_timestamp expiration_time = 0;
++ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ GHashTableIter iter;
++
++ if (self->active_credentials_cache_name != NULL)
++ {
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ verification_level = verify_identity_in_credentials_cache (self,
++ preauth_identity_source,
++ credentials_cache,
++ &start_time,
++ &renewal_time,
++ &expiration_time,
++ error);
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ goto out;
++
++ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ }
++ }
++
++ self->start_time = 0;
++ self->renewal_time = 0;
++ self->expiration_time = 0;
++
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ {
++ krb5_timestamp new_start_time = 0;
++ krb5_timestamp new_renewal_time = 0;
++ krb5_timestamp new_expiration_time = 0;
++
++ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
++ continue;
++
++ g_clear_pointer (preauth_identity_source, g_free);
++ verification_level = verify_identity_in_credentials_cache (self,
++ preauth_identity_source,
++ credentials_cache,
++ &new_start_time,
++ &new_renewal_time,
++ &new_expiration_time,
++ error);
++
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN ||
++ self->active_credentials_cache_name == NULL)
++ {
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_strdup (name);
++ start_time = new_start_time;
++ renewal_time = new_renewal_time;
++ expiration_time = new_expiration_time;
++
++ if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ break;
++ }
++ else if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_iter_remove (&iter);
++ }
++ }
+
++out:
+ G_LOCK (identity_lock);
+ set_start_time (self, start_time);
+ set_renewal_time (self, renewal_time);
+ set_expiration_time (self, expiration_time);
+ G_UNLOCK (identity_lock);
+
+- if (principal != NULL)
+- krb5_free_principal (self->kerberos_context, principal);
+ return verification_level;
+ }
+
+ static gboolean
+ goa_kerberos_identity_is_signed_in (GoaIdentity *identity)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (identity);
+ gboolean is_signed_in = FALSE;
+
+ G_LOCK (identity_lock);
+ if (self->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ is_signed_in = TRUE;
+ G_UNLOCK (identity_lock);
+
+ return is_signed_in;
+ }
+
+ static void
+ identity_interface_init (GoaIdentityInterface *interface)
+ {
+ interface->get_identifier = goa_kerberos_identity_get_identifier;
+ interface->is_signed_in = goa_kerberos_identity_is_signed_in;
+ }
+
+ static void
+ on_expiration_alarm_fired (GoaAlarm *alarm,
+ GoaKerberosIdentity *self)
+ {
+ g_return_if_fail (GOA_IS_ALARM (alarm));
+ g_return_if_fail (GOA_IS_KERBEROS_IDENTITY (self));
+@@ -1052,121 +1186,154 @@ on_kerberos_inquiry (krb5_context kerberos_context,
+ GoaIdentityInquiry *inquiry;
+ krb5_error_code error_code = 0;
+
+ if (number_of_prompts == 0)
+ goto out;
+
+ inquiry = goa_kerberos_identity_inquiry_new (operation->identity,
+ name,
+ banner,
+ prompts,
+ number_of_prompts);
+
+ operation->inquiry_func (inquiry,
+ operation->cancellable,
+ operation->inquiry_data);
+
+ if (goa_identity_inquiry_is_failed (inquiry))
+ error_code = KRB5_LIBOS_CANTREADPWD;
+ else if (!goa_identity_inquiry_is_complete (inquiry))
+ g_cancellable_cancel (operation->cancellable);
+
+ if (g_cancellable_is_cancelled (operation->cancellable))
+ error_code = KRB5_LIBOS_PWDINTR;
+
+ g_object_unref (inquiry);
+
+ out:
+ return error_code;
+ }
+
++static void
++goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache)
++{
++ const char *cache_name;
++
++ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
++
++ if (g_hash_table_contains (self->credentials_caches, cache_name))
++ {
++ krb5_ccache old_credentials_cache;
++
++ old_credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches, cache_name);
++
++ krb5_cc_close (self->kerberos_context, old_credentials_cache);
++ }
++
++ g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), credentials_cache);
++
++ if (self->active_credentials_cache_name == NULL)
++ {
++ self->active_credentials_cache_name = g_strdup (cache_name);
++ }
++}
++
+ static gboolean
+-create_credential_cache (GoaKerberosIdentity *self,
+- GError **error)
++create_credentials_cache (GoaKerberosIdentity *self,
++ GError **error)
+ {
+ krb5_ccache default_cache;
++ krb5_ccache new_cache;
+ const char *cache_type;
+ krb5_error_code error_code;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code == 0)
+ {
+ cache_type = krb5_cc_get_type (self->kerberos_context, default_cache);
+- error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &self->credentials_cache);
++ error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &new_cache);
+ }
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not create credential cache: "));
+
+ return FALSE;
+ }
+
++ goa_kerberos_identity_add_credentials_cache (self, new_cache);
++
+ return TRUE;
+ }
+
+ static gboolean
+ goa_kerberos_identity_update_credentials (GoaKerberosIdentity *self,
+ krb5_principal principal,
+ krb5_creds *new_credentials,
+ GError **error)
+ {
+ krb5_error_code error_code;
++ krb5_ccache credentials_cache;
+
+- if (self->credentials_cache == NULL)
++
++ if (self->active_credentials_cache_name == NULL)
+ {
+- if (!create_credential_cache (self, error))
++ if (!create_credentials_cache (self, error))
+ {
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ }
+
+- error_code = krb5_cc_initialize (self->kerberos_context, self->credentials_cache, principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++
++ error_code = krb5_cc_initialize (self->kerberos_context, credentials_cache, principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not initialize credentials cache: "));
+
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+
+- error_code = krb5_cc_store_cred (self->kerberos_context, self->credentials_cache, new_credentials);
++ error_code = krb5_cc_store_cred (self->kerberos_context, credentials_cache, new_credentials);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_SAVING_CREDENTIALS,
+ error_code,
+ _("Could not store new credentials in credentials cache: "));
+
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+
+ return TRUE;
+ out:
+ return FALSE;
+ }
+
+ static SignInOperation *
+ sign_in_operation_new (GoaKerberosIdentity *identity,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable)
+ {
+ SignInOperation *operation;
+
+ operation = g_slice_new0 (SignInOperation);
+ operation->identity = g_object_ref (identity);
+ operation->inquiry_func = inquiry_func;
+@@ -1327,201 +1494,268 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ g_debug ("GoaKerberosIdentity: identity signed in");
+ signed_in = TRUE;
+ done:
+
+ return signed_in;
+ }
+
+ static void
+ update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+ {
+ char *new_identifier;
+
+ new_identifier = get_identifier (self, NULL);
+ if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+ {
+ g_free (self->identifier);
+ self->identifier = new_identifier;
+ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+ else
+ {
+ g_free (new_identifier);
+ }
+ }
+
++static int
++goa_kerberos_identity_compare (GoaKerberosIdentity *self,
++ GoaKerberosIdentity *new_identity)
++{
++ if (self->cached_verification_level < new_identity->cached_verification_level)
++ return -100;
++
++ if (self->cached_verification_level > new_identity->cached_verification_level)
++ return 100;
++
++ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ return 50;
++
++ if (self->expiration_time < new_identity->expiration_time)
++ return -10;
++
++ if (self->expiration_time > new_identity->expiration_time)
++ return 10;
++
++ if (self->start_time > new_identity->start_time)
++ return -5;
++
++ if (self->start_time < new_identity->start_time)
++ return 5;
++
++ if (self->renewal_time < new_identity->renewal_time)
++ return -1;
++
++ if (self->renewal_time > new_identity->renewal_time)
++ return 1;
++
++ return 0;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
++ int comparison;
++
++ comparison = goa_kerberos_identity_compare (self, new_identity);
++
++ if (new_identity->active_credentials_cache_name != NULL)
++ {
++ krb5_ccache credentials_cache;
++ krb5_ccache copied_cache;
+
+- if (self->credentials_cache != NULL)
+- krb5_cc_close (self->kerberos_context, self->credentials_cache);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
++ new_identity->active_credentials_cache_name);
++ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+- krb5_cc_dup (new_identity->kerberos_context, new_identity->credentials_cache, &self->credentials_cache);
++ if (comparison < 0)
++ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
++
++ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++ }
++
++ if (comparison >= 0)
++ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+-
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
++ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+- if (self->credentials_cache == NULL)
++ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+- error_code = krb5_cc_get_principal (self->kerberos_context, self->credentials_cache, &principal);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+ _("Could not get the default principal: "));
+ goto out;
+ }
+
+ name = goa_kerberos_identity_get_principal_name (self);
+
+- error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, self->credentials_cache, NULL);
++ error_code = krb5_get_renewed_creds (self->kerberos_context, &new_credentials, principal, credentials_cache, NULL);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_RENEWING,
+ error_code,
+ _("Could not get renewed credentials from the KDC for identity %s: "),
+ name);
+ goto free_principal;
+ }
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
++ GHashTableIter iter;
++ const char *name;
++ krb5_ccache credentials_cache;
+ krb5_error_code error_code = 0;
+
+- if (self->credentials_cache != NULL)
++ if (self->active_credentials_cache_name != NULL)
+ {
+- error_code = krb5_cc_destroy (self->kerberos_context, self->credentials_cache);
+- self->credentials_cache = NULL;
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
++ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++
++ if (error_code != 0)
++ {
++ set_and_prefix_error_from_krb5_error_code (self,
++ error,
++ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
++ error_code, _("Could not erase identity: "));
++ }
+ }
+
+- if (error_code != 0)
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+- set_and_prefix_error_from_krb5_error_code (self,
+- error,
+- GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+- error_code, _("Could not erase identity: "));
+- return FALSE;
++ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
++ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
++ g_hash_table_remove_all (self->credentials_caches);
+
+- return TRUE;
++ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
++ krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+-
+- krb5_cc_dup (context, cache, &self->credentials_cache);
+ self->kerberos_context = context;
+
++ krb5_cc_dup (self->kerberos_context, cache, &copied_cache);
++ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++
+ error = NULL;
+ if (!g_initable_init (G_INITABLE (self), NULL, error))
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+
+ return GOA_IDENTITY (self);
+ }
+--
+2.39.3
+
+
+From 02b8df618e1e26ed70b586b3214d1c8f255f9909 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 28 Nov 2022 15:58:09 -0500
+Subject: [PATCH 08/22] kerberos-identity: Clear alarms on temporary identity
+
+When the identity service does a refresh, it creates a new temporary
+identity object to check the credentials, then it merges that
+temporary identity into the preexisting identity object (so the
+pointers don't change).
+
+This has the unfortunate side-effect of arming expiration alarms in
+the temporary object, that can then fire immediately before the object
+is thrown out.
+
+This commit disarms those alarms so they don't fire needlessly.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index dbb5991d..6006385b 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1554,60 +1554,62 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
+
++ clear_alarms (new_identity);
++
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+--
+2.39.3
+
+
+From c0f70c84a8fafbcaf3245765be51a7a027b312e3 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Tue, 29 Nov 2022 12:21:09 -0500
+Subject: [PATCH 09/22] kerberos-identity: Add missing locking
+
+commit c492cbfd861bc773cf8b4c15bc722380355fc4b3 introduced some
+code to goa_kerberos_identity_update that's not protected by the
+identity lock.
+
+It really should be.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 6006385b..b51c9920 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1537,76 +1537,78 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+
+ if (self->start_time > new_identity->start_time)
+ return -5;
+
+ if (self->start_time < new_identity->start_time)
+ return 5;
+
+ if (self->renewal_time < new_identity->renewal_time)
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
++ G_LOCK (identity_lock);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+ g_clear_pointer (&self->active_credentials_cache_name, &g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
++ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+--
+2.39.3
+
+
+From 4d8bba0ec4df1c5fd8533c4b77b62002d5c99ece Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Wed, 30 Nov 2022 13:53:41 -0500
+Subject: [PATCH 10/22] kerberos-identity: Drop the weird ampersand
+
+This commit drops an unnecessary and non-idiomatic ampersand.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b51c9920..55288d24 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1550,61 +1550,61 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+ if (comparison < 0)
+- g_clear_pointer (&self->active_credentials_cache_name, &g_free);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+--
+2.39.3
+
+
+From 1d947c23ae037ea9063064338250251cc52f0b0c Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 15 Dec 2022 14:46:01 -0500
+Subject: [PATCH 11/22] kerberos-identity: Unbreak handling of fresh caches
+
+commit 4acfcc323e986526975ede981673dd173be4e267 attempted to
+avoid an error variable getting stomped all over by returning
+FALSE when encountering the error.
+
+Unfortunately, it's actual legitimate for an error to happen
+in that path and we should proceed anyway.
+
+That can happen when a credential cache is new and not yet
+initialized, so it won't have a principal associated with it
+yet.
+
+This commit changes the problematic code to just pass NULL
+for the error variable, since we don't need it.
+---
+ src/goaidentity/goakerberosidentity.c | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 55288d24..a20c0438 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1094,65 +1094,64 @@ reset_alarms (GoaKerberosIdentity *self)
+ g_clear_pointer (&renewal_time, g_date_time_unref);
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (self->identifier == NULL)
+ {
+- self->identifier = get_identifier (self, error);
+- if (self->identifier == NULL)
+- return FALSE;
++ self->identifier = get_identifier (self, NULL);
+
+- queue_notify (self, &self->identifier_idle_id, "identifier");
++ if (self->identifier != NULL)
++ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+
+ verification_error = NULL;
+ self->cached_verification_level = verify_identity (self, &self->preauth_identity_source, &verification_error);
+
+ switch (self->cached_verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ reset_alarms (self);
+
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ return TRUE;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ return TRUE;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ if (verification_error != NULL)
+ {
+ g_propagate_error (error, verification_error);
+ return FALSE;
+ }
+
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_VERIFYING,
+ _("No associated identification found"));
+ return FALSE;
+--
+2.39.3
+
+
+From 712feaacacf435f807a9f11fc5fa6066d6a052af Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 15 Dec 2022 14:46:01 -0500
+Subject: [PATCH 12/22] kerberos-identity: Fix buglet in update_identity
+
+The update_identity function is supposed to transfer the identity
+form one object to another.
+
+In practice, this is currently always a noop because only objects
+with the same identities get copied to each other.
+
+Nevertheless, there is a bug in the function. It grabs the identity
+from the target object instead of from the source object.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index a20c0438..bc607966 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1480,61 +1480,61 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ g_debug ("GoaKerberosIdentity: identity signed in");
+ signed_in = TRUE;
+ done:
+
+ return signed_in;
+ }
+
+ static void
+ update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+ {
+ char *new_identifier;
+
+- new_identifier = get_identifier (self, NULL);
++ new_identifier = get_identifier (new_identity, NULL);
+ if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+ {
+ g_free (self->identifier);
+ self->identifier = new_identifier;
+ queue_notify (self, &self->identifier_idle_id, "identifier");
+ }
+ else
+ {
+ g_free (new_identifier);
+ }
+ }
+
+ static int
+ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ if (self->cached_verification_level < new_identity->cached_verification_level)
+ return -100;
+
+ if (self->cached_verification_level > new_identity->cached_verification_level)
+ return 100;
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ return 50;
+
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+--
+2.39.3
+
+
+From 4f3d89260a4b600e26a67a671039ea03692fb684 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 15 Dec 2022 16:27:27 -0500
+Subject: [PATCH 13/22] goakerberosidentity: Fix crash when erasing credentials
+
+Right now when erasing an identity we erase the
+active credentials first and then the inactive
+ones.
+
+We neglect to take the active one out of the hash
+table, though, so it gets destroyed twice.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index bc607966..46a6fb22 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1692,60 +1692,62 @@ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ goto free_principal;
+ }
+
+ g_debug ("GoaKerberosIdentity: identity %s renewed", name);
+ renewed = TRUE;
+
+ free_principal:
+ krb5_free_principal (self->kerberos_context, principal);
+
+ out:
+ g_free (name);
+
+ return renewed;
+ }
+
+ gboolean
+ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ {
+ GHashTableIter iter;
+ const char *name;
+ krb5_ccache credentials_cache;
+ krb5_error_code error_code = 0;
+
+ if (self->active_credentials_cache_name != NULL)
+ {
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
+ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+ error_code, _("Could not erase identity: "));
+ }
+ }
+
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
+ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
+ g_hash_table_remove_all (self->credentials_caches);
+
+ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
+ krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+ self->kerberos_context = context;
+--
+2.39.3
+
+
+From d36f9eb09ee157a01ad66f3950076a46cda94094 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 15 Dec 2022 15:35:49 -0500
+Subject: [PATCH 14/22] goakerberosidentity: Explicitly switch to credentials
+ cache when needed
+
+If we're updating a credentials cache and decide
+it should be the new default for an identity, and
+the old credentials cache was the default cache
+for the cache collection then we should make the
+new credential cache the default cache for the
+collection, too.
+
+This commit adds that. It also makes the new
+credentials cache the default if there wasn't a
+valid default set already. This brings consistency
+to differences in behavior from different kerberos
+ccache types.
+---
+ src/goaidentity/goakerberosidentity.c | 63 +++++++++++++++++++++++++--
+ 1 file changed, 60 insertions(+), 3 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 46a6fb22..4fe4b70b 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1527,100 +1527,157 @@ goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+ if (self->cached_verification_level < new_identity->cached_verification_level)
+ return -100;
+
+ if (self->cached_verification_level > new_identity->cached_verification_level)
+ return 100;
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ return 50;
+
+ if (self->expiration_time < new_identity->expiration_time)
+ return -10;
+
+ if (self->expiration_time > new_identity->expiration_time)
+ return 10;
+
+ if (self->start_time > new_identity->start_time)
+ return -5;
+
+ if (self->start_time < new_identity->start_time)
+ return 5;
+
+ if (self->renewal_time < new_identity->renewal_time)
+ return -1;
+
+ if (self->renewal_time > new_identity->renewal_time)
+ return 1;
+
+ return 0;
+ }
+
++static char *
++get_default_cache_name (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *default_cache_name;
++ char *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ krb5_free_unparsed_name (self->kerberos_context, principal_name);
++
++ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return default_cache_name;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
++
++ old_verification_level = self->cached_verification_level;
++ new_verification_level = new_identity->cached_verification_level;
++
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
++ g_autofree char *default_cache_name = NULL;
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
++ gboolean should_switch_to_new_credentials_cache = FALSE;
++
++ default_cache_name = get_default_cache_name (self);
++
++ if (default_cache_name == NULL)
++ should_switch_to_new_credentials_cache = TRUE;
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+ new_identity->active_credentials_cache_name);
+ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
++ if (g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0)
++ {
++ if ((comparison < 0) ||
++ (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN))
++ should_switch_to_new_credentials_cache = TRUE;
++ }
++
+ if (comparison < 0)
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ {
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
++ }
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++
++ if (should_switch_to_new_credentials_cache)
++ krb5_cc_switch (self->kerberos_context, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+ if (comparison >= 0)
+ return;
+
+ G_LOCK (identity_lock);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+- old_verification_level = self->cached_verification_level;
+- new_verification_level = new_identity->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ reset_alarms (self);
+ else
+ clear_alarms (self);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+--
+2.39.3
+
+
+From bef93518e9f23d69c01c714ec9c8a6f2c012a08c Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 16 Jan 2023 15:00:36 -0500
+Subject: [PATCH 15/22] goakerberosidentity: Fix automatic reinitialization
+
+The identity service has the ability to automatically fetch a new ticket
+granting ticket from the KDC when the existing one expires, provided the
+user keeps their kerberos password in GNOME keyring.
+
+Unfortunately, commit aca400799c225a84e5d0fc90efb206c8f1d48bc3
+inadvertently broke this feature in some cases.
+
+When deciding whether or not to make a new credentials cache for a
+principal the active one it looks at various characteristics of the
+competing credentials to decide which cache is better.
+
+For instance, if one credentials cache has a ticket that's valid and
+signed in, but the other credentials cache only has an expired ticket,
+then obviously the one that's valid and signed in gets picked to be
+active.
+
+Likewise, if one is expiring in 10 minutes and one is expiring in
+24 hours, the one that expires in 24 hours will be treated as better.
+
+This comparison, only makes sense, though when looking at two different
+credentials caches. If we're updating a preexisting credentials cache,
+then we're actually just comparing up to date data with out of date
+data. In that case, we need to proceed even if new newer view of the
+credentials look worse than the older view of those credentials.
+Unfortunately, the buggy commit neglected to account for that.
+
+This commit fixes that problem and related problems, by more
+thoroughly and systematically checking all the permutations of
+credentials in the old credentials cache for the identity, the
+new credentials cache for the identity, and the default credentials
+cache. It also adds a lot more logging for clarity.
+---
+ src/goaidentity/goakerberosidentity.c | 198 ++++++++++++++++++++++++--
+ 1 file changed, 183 insertions(+), 15 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 4fe4b70b..d046a8a4 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1562,130 +1562,298 @@ get_default_cache_name (GoaKerberosIdentity *self)
+ krb5_principal principal;
+ char *default_cache_name;
+ char *principal_name;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code != 0)
+ return NULL;
+
+ /* Return NULL if the default cache doesn't pass basic sanity checks
+ */
+ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+
+ if (error_code != 0)
+ return NULL;
+
+ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
+ krb5_free_principal (self->kerberos_context, principal);
+
+ if (error_code != 0)
+ return NULL;
+
+ krb5_free_unparsed_name (self->kerberos_context, principal_name);
+
+ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+ krb5_cc_close (self->kerberos_context, default_cache);
+
+ return default_cache_name;
+ }
+
++static char *
++get_default_principal (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *unparsed_principal, *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_principal);
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
++
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return principal_name;
++}
++
+ void
+ goa_kerberos_identity_update (GoaKerberosIdentity *self,
+ GoaKerberosIdentity *new_identity)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
++ gboolean should_set_cache_active = FALSE;
+ gboolean time_changed = FALSE;
+ char *preauth_identity_source = NULL;
++ g_autofree char *default_principal = NULL;
+ int comparison;
+
+ G_LOCK (identity_lock);
+
++ g_debug ("GoaKerberosIdentity: Evaluating updated credentials for identity %s "
++ "(old credentials cache name: %s, new credentials cache name: %s)",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
+ old_verification_level = self->cached_verification_level;
+ new_verification_level = new_identity->cached_verification_level;
+
++ default_principal = get_default_principal (self);
+ comparison = goa_kerberos_identity_compare (self, new_identity);
+
+ if (new_identity->active_credentials_cache_name != NULL)
+ {
+ g_autofree char *default_cache_name = NULL;
+ krb5_ccache credentials_cache;
+ krb5_ccache copied_cache;
+- gboolean should_switch_to_new_credentials_cache = FALSE;
++ gboolean should_set_cache_as_default = FALSE;
++ gboolean is_default_principal = FALSE, is_default_cache = FALSE;
++ gboolean cache_already_active = FALSE;
++
++ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+
+ default_cache_name = get_default_cache_name (self);
++ is_default_cache = g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0;
++ cache_already_active = g_strcmp0 (self->active_credentials_cache_name, new_identity->active_credentials_cache_name) == 0;
+
+- if (default_cache_name == NULL)
+- should_switch_to_new_credentials_cache = TRUE;
++ g_debug ("GoaKerberosIdentity: Default credentials cache is '%s' (is %sus, is %sactive)", default_cache_name, is_default_cache? "" : "not ", cache_already_active? "" : "not ");
+
+- credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+- new_identity->active_credentials_cache_name);
+- krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
++ if (default_principal == NULL)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
+
+- if (g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0)
++ g_debug ("GoaKerberosIdentity: Setting default credentials cache to '%s' (principal %s) "
++ "because there is no active default",
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ else if (!is_default_principal)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' (principal %s) "
++ "because identity is currently not default (credentials already active? %s)",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ cache_already_active? "yes" : "no");
++ should_set_cache_as_default = FALSE;
++
++ if (comparison < 0)
++ {
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to credentials cache '%s' "
++ "because it has better credentials",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to "
++ "'%s' because it is newer and is otherwise just as good",
++ self->identifier,
++ self->active_credentials_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ else
++ {
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not switching identity %s from credentials cache '%s' to '%s' "
++ "because it has less good credentials",
++ self->identifier,
++ default_cache_name,
++ new_identity->active_credentials_cache_name);
++ }
++ }
++ else if (cache_already_active)
++ {
++ if (is_default_cache)
++ {
++ should_set_cache_as_default = FALSE;
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not setting default credentials cache to '%s' "
++ "because cache is already active for identity %s and identity is default",
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ else
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default and cache is supposed to be active already but isn't",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier);
++ }
++ }
++ else
+ {
+- if ((comparison < 0) ||
+- (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN))
+- should_switch_to_new_credentials_cache = TRUE;
++ if (comparison < 0)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default and the cache has better credentials than those "
++ "in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
++ else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ should_set_cache_as_default = TRUE;
++ should_set_cache_active = TRUE;
++
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default, and the cache has newer, and otherwise "
++ "just as good credentials as those in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
++ else
++ {
++ should_set_cache_as_default = FALSE;
++ should_set_cache_active = FALSE;
++
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' "
++ "because identity %s is default but newer credentials aren't as good as those in '%s'",
++ default_cache_name,
++ new_identity->active_credentials_cache_name,
++ self->identifier,
++ self->active_credentials_cache_name);
++ }
+ }
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
++ new_identity->active_credentials_cache_name);
++ krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+
+- if (comparison < 0)
++ if (should_set_cache_active)
+ {
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+ self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
+ }
+
+ goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+
+- if (should_switch_to_new_credentials_cache)
++ if (should_set_cache_as_default)
+ krb5_cc_switch (self->kerberos_context, copied_cache);
+ }
+ G_UNLOCK (identity_lock);
+
+ clear_alarms (new_identity);
+
+- if (comparison >= 0)
++ if (!should_set_cache_active)
+ return;
+
+ G_LOCK (identity_lock);
++ g_debug ("GoaKerberosIdentity: Setting identity %s to use updated credentials in credentials cache '%s'",
++ self->identifier, self->active_credentials_cache_name);
+ update_identifier (self, new_identity);
+ time_changed |= set_start_time (self, new_identity->start_time);
+ time_changed |= set_renewal_time (self, new_identity->renewal_time);
+ time_changed |= set_expiration_time (self, new_identity->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- reset_alarms (self);
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
++ reset_alarms (self);
++ }
+ else
+- clear_alarms (self);
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
++ clear_alarms (self);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
+ }
+
+ G_LOCK (identity_lock);
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+--
+2.39.3
+
+
+From 829c24d0ecbac452c38d5217b8465457ea0cfb43 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 19 Jan 2023 11:31:14 -0500
+Subject: [PATCH 16/22] goakerberosidentity: Fall back to stale credentials if
+ active credentials get destroyed
+
+At the moment, the identity service doesn't recognize when a credentials
+cache gets kdestroy'd explicitly by the user. It knows when a principal
+is purged from all credential caches, but it doesn't know when a
+specific cache is removed.
+
+This means it doesn't fall back properly to an older credential crash if
+the active cache gets destroyed.
+
+This commit addresses that problem by reachitecting things a bit.
+
+Previously, cache updates were processed by creating a new transient
+GoaKerberosIdentity and then merging it into an existing identity using
+goa_kerberos_identity_update. Using a full blown GoaKerberosIdentity
+object as a wrapper around a lone credentials cache is kind of a weird
+pattern and doesn't facillate processing cache removal, since we can't
+create a transient identity for what's not there.
+
+This commit exports goa_kerberos_identity_add_credentials_cache as
+public api as an alternative to the merging transient identity flow.
+
+It also also adds a goa_kerberos_identity_refresh function to be called
+after goa_kerberos_identity_add_credentials_cache is preformed for all
+new caches. It handles merging in the new credentials from the updated
+credentials caches, and also handles cache removal.
+
+A benefit of this new flow is much of the guts of
+goa_kerberos_identity_update have now been moved to verify_identity
+allowing for some code deduplication. Previously verify_identity was
+only called at object construction time, though, so this commit adds
+more locking to accomodate it get called while the identity is in use by
+other threads.
+---
+ src/goaidentity/goakerberosidentity.c | 706 +++++++++----------
+ src/goaidentity/goakerberosidentity.h | 8 +-
+ src/goaidentity/goakerberosidentitymanager.c | 132 ++--
+ 3 files changed, 419 insertions(+), 427 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index d046a8a4..b5cbcecd 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -619,65 +619,62 @@ set_expiration_time (GoaKerberosIdentity *self,
+ self->expiration_time = expiration_time;
+ queue_notify (self, &self->expiration_time_idle_id, "expiration-timestamp");
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ static void
+ examine_credentials (GoaKerberosIdentity *self,
+ krb5_creds *credentials,
+ krb5_timestamp *start_time,
+ krb5_timestamp *renewal_time,
+ krb5_timestamp *expiration_time,
+ gboolean *are_expired)
+ {
+ krb5_timestamp credentials_start_time;
+ krb5_timestamp credentials_end_time;
+ krb5_timestamp current_time;
+
+ G_LOCK (identity_lock);
+
+ if (credentials->times.starttime != 0)
+ credentials_start_time = credentials->times.starttime;
+ else
+ credentials_start_time = credentials->times.authtime;
+
+ *renewal_time = credentials->times.renew_till;
+
+ credentials_end_time = credentials->times.endtime;
+
+- if (self->start_time == 0)
+- *start_time = credentials_start_time;
+- else
+- *start_time = MIN (self->start_time, credentials_start_time);
+- *expiration_time = MAX (credentials->times.endtime, self->expiration_time);
++ *start_time = credentials_start_time;
++ *expiration_time = credentials->times.endtime;
+ G_UNLOCK (identity_lock);
+
+ current_time = get_current_time (self);
+
+ if (current_time < credentials_start_time ||
+ credentials_end_time <= current_time)
+ *are_expired = TRUE;
+ else
+ *are_expired = FALSE;
+ }
+
+ static VerificationLevel
+ verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ krb5_ccache credentials_cache,
+ krb5_timestamp *start_time,
+ krb5_timestamp *renewal_time,
+ krb5_timestamp *expiration_time,
+ GError **error)
+ {
+ krb5_principal principal = NULL;
+ krb5_cc_cursor cursor;
+ krb5_creds credentials;
+ krb5_error_code error_code;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+
+ g_debug ("GoaKerberosIdentity: Verifying identity in credentials cache '%s'",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+@@ -771,146 +768,395 @@ verify_identity_in_credentials_cache (GoaKerberosIdentity *self,
+ out:
+ switch (verification_level)
+ {
+ case VERIFICATION_LEVEL_EXISTS:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are out of date",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_SIGNED_IN:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are valid",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_UNVERIFIED:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' are missing",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+
+ case VERIFICATION_LEVEL_ERROR:
+ default:
+ g_debug ("GoaKerberosIdentity: Credentials in credentials cache '%s' could not be validated",
+ krb5_cc_get_name (self->kerberos_context, credentials_cache));
+ break;
+ }
+
+ if (principal != NULL)
+ krb5_free_principal (self->kerberos_context, principal);
+ return verification_level;
+ }
+
++static char *
++get_default_principal (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *unparsed_principal, *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_principal);
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
++
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return principal_name;
++}
++
++static char *
++get_default_cache_name (GoaKerberosIdentity *self)
++{
++ int error_code;
++ krb5_ccache default_cache;
++ krb5_principal principal;
++ char *default_cache_name;
++ char *principal_name;
++
++ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
++
++ if (error_code != 0)
++ return NULL;
++
++ /* Return NULL if the default cache doesn't pass basic sanity checks
++ */
++ error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ krb5_free_unparsed_name (self->kerberos_context, principal_name);
++
++ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
++ krb5_cc_close (self->kerberos_context, default_cache);
++
++ return default_cache_name;
++}
++
+ static VerificationLevel
+ verify_identity (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ GError **error)
+ {
+ krb5_ccache credentials_cache;
++ g_autofree char *default_principal = NULL;
++ g_autofree char *default_credentials_cache_name = NULL;
++ gboolean is_default_principal;
++ gboolean is_default_credentials_cache;
++ gboolean should_switch_default_credentials_cache = FALSE;
++ gboolean time_changed = FALSE;
+ const char *name;
+- krb5_timestamp start_time = 0;
+- krb5_timestamp renewal_time = 0;
+- krb5_timestamp expiration_time = 0;
+- VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ krb5_timestamp best_start_time = 0;
++ krb5_timestamp best_renewal_time = 0;
++ krb5_timestamp best_expiration_time = 0;
++ g_autofree char *best_preauth_identity_source = NULL;
++ g_autofree char *best_credentials_cache_name = NULL;
++ VerificationLevel old_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ VerificationLevel best_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ GHashTableIter iter;
+
+ if (self->active_credentials_cache_name != NULL)
+ {
++ G_LOCK (identity_lock);
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
++ G_UNLOCK (identity_lock);
+
+- verification_level = verify_identity_in_credentials_cache (self,
+- preauth_identity_source,
+- credentials_cache,
+- &start_time,
+- &renewal_time,
+- &expiration_time,
+- error);
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ best_verification_level = verify_identity_in_credentials_cache (self,
++ &best_preauth_identity_source,
++ credentials_cache,
++ &best_start_time,
++ &best_renewal_time,
++ &best_expiration_time,
++ error);
++ G_LOCK (identity_lock);
++ best_credentials_cache_name = g_strdup (self->active_credentials_cache_name);
++ G_UNLOCK (identity_lock);
++
++ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ goto out;
+
+- if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++ if (best_verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
++ best_verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+- krb5_cc_close (self->kerberos_context, credentials_cache);
+- g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ g_clear_pointer (&best_credentials_cache_name, g_free);
++
++ G_LOCK (identity_lock);
++ if (self->identifier != NULL)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ }
++ G_UNLOCK (identity_lock);
+ }
+ }
+
+- self->start_time = 0;
+- self->renewal_time = 0;
+- self->expiration_time = 0;
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
++ G_UNLOCK (identity_lock);
+
++ G_LOCK (identity_lock);
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ krb5_timestamp new_start_time = 0;
+ krb5_timestamp new_renewal_time = 0;
+ krb5_timestamp new_expiration_time = 0;
++ g_autofree char *new_preauth_identity_source = NULL;
++ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
++ gboolean has_better_credentials = FALSE;
+
+ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
+ continue;
+
+- g_clear_pointer (preauth_identity_source, g_free);
++ G_UNLOCK (identity_lock);
++
++ if (preauth_identity_source != NULL)
++ g_clear_pointer (preauth_identity_source, g_free);
++
+ verification_level = verify_identity_in_credentials_cache (self,
+- preauth_identity_source,
++ &new_preauth_identity_source,
+ credentials_cache,
+ &new_start_time,
+ &new_renewal_time,
+ &new_expiration_time,
+ error);
+
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN ||
+- self->active_credentials_cache_name == NULL)
++ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
++ verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
+- self->active_credentials_cache_name = g_strdup (name);
+- start_time = new_start_time;
+- renewal_time = new_renewal_time;
+- expiration_time = new_expiration_time;
+-
+- if (verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- break;
++ G_LOCK (identity_lock);
++ if (self->identifier != NULL)
++ {
++ krb5_cc_close (self->kerberos_context, credentials_cache);
++ g_hash_table_iter_remove (&iter);
++ }
++
++ /* Note: The lock is held while iterating */
++ continue;
+ }
+- else if (verification_level == VERIFICATION_LEVEL_UNVERIFIED)
++
++ if (best_verification_level < verification_level)
++ has_better_credentials = TRUE;
++ else if (best_verification_level > verification_level)
++ has_better_credentials = FALSE;
++ else if (best_expiration_time < new_expiration_time)
++ has_better_credentials = TRUE;
++ else if (best_expiration_time > new_expiration_time)
++ has_better_credentials = FALSE;
++ else if (best_start_time > new_start_time)
++ has_better_credentials = TRUE;
++ else if (best_start_time > new_start_time)
++ has_better_credentials = FALSE;
++ else if (best_renewal_time < new_renewal_time)
++ has_better_credentials = TRUE;
++ else if (best_renewal_time > new_renewal_time)
++ has_better_credentials = FALSE;
++ else
++ has_better_credentials = FALSE;
++
++ if (has_better_credentials)
+ {
+- krb5_cc_close (self->kerberos_context, credentials_cache);
+- g_hash_table_iter_remove (&iter);
++ best_verification_level = verification_level;
++ best_start_time = new_start_time;
++ best_renewal_time = new_renewal_time;
++ best_expiration_time = new_expiration_time;
++
++ g_clear_pointer (&best_preauth_identity_source, g_free);
++ best_preauth_identity_source = g_steal_pointer (&new_preauth_identity_source);
++
++ g_clear_pointer (&best_credentials_cache_name, g_free);
++ best_credentials_cache_name = g_strdup (name);
+ }
++
++ G_LOCK (identity_lock);
++ }
++ G_UNLOCK (identity_lock);
++
++ if (best_credentials_cache_name == NULL)
++ {
++ g_hash_table_iter_init (&iter, self->credentials_caches);
++ if (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
++ best_credentials_cache_name = g_strdup (name);
+ }
+
+ out:
++
+ G_LOCK (identity_lock);
+- set_start_time (self, start_time);
+- set_renewal_time (self, renewal_time);
+- set_expiration_time (self, expiration_time);
++ g_clear_pointer (&self->active_credentials_cache_name, g_free);
++ self->active_credentials_cache_name = g_steal_pointer (&best_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+- return verification_level;
++ *preauth_identity_source = g_steal_pointer (&best_preauth_identity_source);
++
++ if (best_verification_level > VERIFICATION_LEVEL_UNVERIFIED)
++ {
++ G_LOCK (identity_lock);
++ time_changed |= set_start_time (self, best_start_time);
++ time_changed |= set_renewal_time (self, best_renewal_time);
++ time_changed |= set_expiration_time (self, best_expiration_time);
++ G_UNLOCK (identity_lock);
++
++ if (time_changed)
++ {
++ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
++ reset_alarms (self);
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
++ clear_alarms (self);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
++ }
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: identity is unverified, clearing alarms");
++ clear_alarms (self);
++ }
++
++ if (best_verification_level != old_verification_level)
++ {
++ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
++ best_verification_level == VERIFICATION_LEVEL_EXISTS)
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++
++ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
++ }
++ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
++ best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++
++ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
++ }
++ else
++ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ G_UNLOCK (identity_lock);
++ }
++ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ }
++
++ default_principal = get_default_principal (self);
++ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
++
++ default_credentials_cache_name = get_default_cache_name (self);
++ is_default_credentials_cache = g_strcmp0 (default_credentials_cache_name, self->active_credentials_cache_name) == 0;
++
++ if (self->active_credentials_cache_name == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s has no active credentials cache to switch to", self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (self->identifier == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' because it is not yet initialized", self->active_credentials_cache_name);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (default_principal == NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache to '%s' (identity %s) because there is currently no default", self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = TRUE;
++ }
++ else if (!is_default_principal)
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s is not the default identity", self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++ else if (!is_default_credentials_cache)
++ {
++ g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' because identity %s is the default, and that credentials cache is supposed to be the active cache for that identity",
++ default_credentials_cache_name, self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = TRUE;
++ }
++ else
++ {
++ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' for identity %s because it's already the default", self->active_credentials_cache_name, self->identifier);
++ should_switch_default_credentials_cache = FALSE;
++ }
++
++ if (should_switch_default_credentials_cache)
++ {
++ G_LOCK (identity_lock);
++ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
++ self->active_credentials_cache_name);
++ krb5_cc_switch (self->kerberos_context, credentials_cache);
++ G_UNLOCK (identity_lock);
++ }
++
++ return best_verification_level;
+ }
+
+ static gboolean
+ goa_kerberos_identity_is_signed_in (GoaIdentity *identity)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (identity);
+ gboolean is_signed_in = FALSE;
+
+ G_LOCK (identity_lock);
+ if (self->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ is_signed_in = TRUE;
+ G_UNLOCK (identity_lock);
+
+ return is_signed_in;
+ }
+
+ static void
+ identity_interface_init (GoaIdentityInterface *interface)
+ {
+ interface->get_identifier = goa_kerberos_identity_get_identifier;
+ interface->is_signed_in = goa_kerberos_identity_is_signed_in;
+ }
+
+ static void
+ on_expiration_alarm_fired (GoaAlarm *alarm,
+ GoaKerberosIdentity *self)
+ {
+ g_return_if_fail (GOA_IS_ALARM (alarm));
+ g_return_if_fail (GOA_IS_KERBEROS_IDENTITY (self));
+
+@@ -1059,60 +1305,62 @@ reset_alarms (GoaKerberosIdentity *self)
+ GDateTime *expiring_time = NULL;
+ GDateTime *latest_possible_renewal_time = NULL;
+ GDateTime *renewal_time = NULL;
+
+ G_LOCK (identity_lock);
+ start_time = g_date_time_new_from_unix_local (self->start_time);
+ if (self->renewal_time != 0)
+ latest_possible_renewal_time = g_date_time_new_from_unix_local (self->renewal_time);
+ expiration_time = g_date_time_new_from_unix_local (self->expiration_time);
+ G_UNLOCK (identity_lock);
+
+ /* Let the user reauthenticate 10 min before expiration */
+ expiring_time = g_date_time_add_minutes (expiration_time, -10);
+
+ if (latest_possible_renewal_time != NULL)
+ {
+ GTimeSpan lifespan;
+
+ lifespan = g_date_time_difference (expiration_time, start_time);
+
+ /* Try to quietly auto-renew halfway through so in ideal configurations
+ * the ticket is never more than halfway to unrenewable
+ */
+ renewal_time = g_date_time_add (start_time, lifespan / 2);
+ }
+
+ disconnect_alarm_signals (self);
+
+ if (renewal_time != NULL)
+ reset_alarm (self, &self->renewal_alarm, renewal_time);
++ else if (self->renewal_alarm != NULL)
++ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+
+ reset_alarm (self, &self->expiring_alarm, expiring_time);
+ reset_alarm (self, &self->expiration_alarm, expiration_time);
+
+ g_clear_pointer (&expiring_time, g_date_time_unref);
+ g_clear_pointer (&renewal_time, g_date_time_unref);
+ g_clear_pointer (&expiration_time, g_date_time_unref);
+ g_clear_pointer (&latest_possible_renewal_time, g_date_time_unref);
+ g_clear_pointer (&start_time, g_date_time_unref);
+
+ connect_alarm_signals (self);
+ }
+
+ static void
+ clear_alarms (GoaKerberosIdentity *self)
+ {
+ disconnect_alarm_signals (self);
+ clear_alarm_and_unref_on_idle (self, &self->renewal_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiring_alarm);
+ clear_alarm_and_unref_on_idle (self, &self->expiration_alarm);
+ }
+
+ static gboolean
+ goa_kerberos_identity_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ GoaKerberosIdentity *self = GOA_KERBEROS_IDENTITY (initable);
+ GError *verification_error;
+
+@@ -1185,114 +1433,137 @@ on_kerberos_inquiry (krb5_context kerberos_context,
+ GoaIdentityInquiry *inquiry;
+ krb5_error_code error_code = 0;
+
+ if (number_of_prompts == 0)
+ goto out;
+
+ inquiry = goa_kerberos_identity_inquiry_new (operation->identity,
+ name,
+ banner,
+ prompts,
+ number_of_prompts);
+
+ operation->inquiry_func (inquiry,
+ operation->cancellable,
+ operation->inquiry_data);
+
+ if (goa_identity_inquiry_is_failed (inquiry))
+ error_code = KRB5_LIBOS_CANTREADPWD;
+ else if (!goa_identity_inquiry_is_complete (inquiry))
+ g_cancellable_cancel (operation->cancellable);
+
+ if (g_cancellable_is_cancelled (operation->cancellable))
+ error_code = KRB5_LIBOS_PWDINTR;
+
+ g_object_unref (inquiry);
+
+ out:
+ return error_code;
+ }
+
+-static void
++gboolean
++goa_kerberos_identity_has_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache)
++{
++ const char *cache_name;
++
++ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
++
++ return g_hash_table_contains (self->credentials_caches, cache_name);
++}
++
++void
+ goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
+ krb5_ccache credentials_cache)
+ {
+ const char *cache_name;
++ krb5_ccache copied_cache;
+
+ cache_name = krb5_cc_get_name (self->kerberos_context, credentials_cache);
+
+ if (g_hash_table_contains (self->credentials_caches, cache_name))
+ {
+ krb5_ccache old_credentials_cache;
+
++ g_debug ("GoaKerberosIdentity: Updating credentials in credentials cache '%s' for identity %s ", cache_name, self->identifier);
++
+ old_credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches, cache_name);
+
+ krb5_cc_close (self->kerberos_context, old_credentials_cache);
+ }
++ else
++ {
++ if (self->identifier != NULL)
++ g_debug ("GoaKerberosIdentity: Associating identity %s with new credentials cache '%s'", self->identifier, cache_name);
++ else
++ g_debug ("GoaKerberosIdentity: Associating new identity with new credentials cache '%s'", cache_name);
++ }
+
+- g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), credentials_cache);
++ krb5_cc_dup (self->kerberos_context, credentials_cache, &copied_cache);
++ g_hash_table_replace (self->credentials_caches, g_strdup (cache_name), copied_cache);
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ self->active_credentials_cache_name = g_strdup (cache_name);
+ }
+ }
+
+ static gboolean
+ create_credentials_cache (GoaKerberosIdentity *self,
+ GError **error)
+ {
+ krb5_ccache default_cache;
+ krb5_ccache new_cache;
+ const char *cache_type;
+ krb5_error_code error_code;
+
+ error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+
+ if (error_code == 0)
+ {
+ cache_type = krb5_cc_get_type (self->kerberos_context, default_cache);
+ error_code = krb5_cc_new_unique (self->kerberos_context, cache_type, NULL, &new_cache);
+ }
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ _("Could not create credential cache: "));
+
+ return FALSE;
+ }
+
+ goa_kerberos_identity_add_credentials_cache (self, new_cache);
++ krb5_cc_close (self->kerberos_context, new_cache);
+
+ return TRUE;
+ }
+
+ static gboolean
+ goa_kerberos_identity_update_credentials (GoaKerberosIdentity *self,
+ krb5_principal principal,
+ krb5_creds *new_credentials,
+ GError **error)
+ {
+ krb5_error_code error_code;
+ krb5_ccache credentials_cache;
+
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ if (!create_credentials_cache (self, error))
+ {
+ krb5_free_cred_contents (self->kerberos_context, new_credentials);
+ goto out;
+ }
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+
+ error_code = krb5_cc_initialize (self->kerberos_context, credentials_cache, principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+@@ -1348,81 +1619,78 @@ sign_in_operation_new (GoaKerberosIdentity *identity,
+ }
+
+ static void
+ sign_in_operation_free (SignInOperation *operation)
+ {
+ g_object_unref (operation->identity);
+ g_object_unref (operation->cancellable);
+
+ g_slice_free (SignInOperation, operation);
+ }
+
+ gboolean
+ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ const char *principal_name,
+ gconstpointer initial_password,
+ const char *preauth_source,
+ GoaIdentitySignInFlags flags,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error)
+ {
+ SignInOperation *operation;
+ krb5_principal principal;
+ krb5_error_code error_code;
+ krb5_creds new_credentials;
+ krb5_get_init_creds_opt *options;
+ krb5_deltat start_time;
+ char *service_name;
+- gboolean signed_in;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ error_code = krb5_get_init_creds_opt_alloc (self->kerberos_context, &options);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_ALLOCATING_CREDENTIALS,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ return FALSE;
+ }
+
+- signed_in = FALSE;
+-
+ operation = sign_in_operation_new (self,
+ inquiry_func,
+ inquiry_data,
+ destroy_notify,
+ cancellable);
+
+ if (g_strcmp0 (self->identifier, principal_name) != 0)
+ {
+ g_free (self->identifier);
+ self->identifier = g_strdup (principal_name);
+ }
+
+ error_code = krb5_parse_name (self->kerberos_context, principal_name, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_PARSING_IDENTIFIER,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ return FALSE;
+ }
+
+ if ((flags & GOA_IDENTITY_SIGN_IN_FLAGS_DISALLOW_FORWARDING) == 0)
+ krb5_get_init_creds_opt_set_forwardable (options, TRUE);
+
+ if ((flags & GOA_IDENTITY_SIGN_IN_FLAGS_DISALLOW_PROXYING) == 0)
+@@ -1468,426 +1736,128 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_AUTHENTICATION_FAILED,
+ error_code,
+ "%s",
+ ""); /* Silence -Wformat-zero-length */
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+
+ if (destroy_notify)
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+- g_debug ("GoaKerberosIdentity: identity signed in");
+- signed_in = TRUE;
+ done:
+
+- return signed_in;
+-}
+-
+-static void
+-update_identifier (GoaKerberosIdentity *self, GoaKerberosIdentity *new_identity)
+-{
+- char *new_identifier;
++ goa_kerberos_identity_refresh (self);
+
+- new_identifier = get_identifier (new_identity, NULL);
+- if (g_strcmp0 (self->identifier, new_identifier) != 0 && new_identifier != NULL)
+- {
+- g_free (self->identifier);
+- self->identifier = new_identifier;
+- queue_notify (self, &self->identifier_idle_id, "identifier");
+- }
+- else
++ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ {
+- g_free (new_identifier);
++ g_debug ("GoaKerberosIdentity: Identity '%s' could not be signed in", principal_name);
++ return FALSE;
+ }
+-}
+-
+-static int
+-goa_kerberos_identity_compare (GoaKerberosIdentity *self,
+- GoaKerberosIdentity *new_identity)
+-{
+- if (self->cached_verification_level < new_identity->cached_verification_level)
+- return -100;
+-
+- if (self->cached_verification_level > new_identity->cached_verification_level)
+- return 100;
+-
+- if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- return 50;
+-
+- if (self->expiration_time < new_identity->expiration_time)
+- return -10;
+-
+- if (self->expiration_time > new_identity->expiration_time)
+- return 10;
+-
+- if (self->start_time > new_identity->start_time)
+- return -5;
+-
+- if (self->start_time < new_identity->start_time)
+- return 5;
+-
+- if (self->renewal_time < new_identity->renewal_time)
+- return -1;
+-
+- if (self->renewal_time > new_identity->renewal_time)
+- return 1;
+-
+- return 0;
+-}
+-
+-static char *
+-get_default_cache_name (GoaKerberosIdentity *self)
+-{
+- int error_code;
+- krb5_ccache default_cache;
+- krb5_principal principal;
+- char *default_cache_name;
+- char *principal_name;
+-
+- error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- /* Return NULL if the default cache doesn't pass basic sanity checks
+- */
+- error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &principal_name);
+- krb5_free_principal (self->kerberos_context, principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- krb5_free_unparsed_name (self->kerberos_context, principal_name);
+-
+- default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+- krb5_cc_close (self->kerberos_context, default_cache);
+-
+- return default_cache_name;
+-}
+-
+-static char *
+-get_default_principal (GoaKerberosIdentity *self)
+-{
+- int error_code;
+- krb5_ccache default_cache;
+- krb5_principal principal;
+- char *unparsed_principal, *principal_name;
+-
+- error_code = krb5_cc_default (self->kerberos_context, &default_cache);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- /* Return NULL if the default cache doesn't pass basic sanity checks
+- */
+- error_code = krb5_cc_get_principal (self->kerberos_context, default_cache, &principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_principal);
+- krb5_free_principal (self->kerberos_context, principal);
+-
+- if (error_code != 0)
+- return NULL;
+-
+- principal_name = g_strdup (unparsed_principal);
+- krb5_free_unparsed_name (self->kerberos_context, unparsed_principal);
+-
+- krb5_cc_close (self->kerberos_context, default_cache);
+
+- return principal_name;
++ g_debug ("GoaKerberosIdentity: Identity '%s' signed in", principal_name);
++ return TRUE;
+ }
+
+ void
+-goa_kerberos_identity_update (GoaKerberosIdentity *self,
+- GoaKerberosIdentity *new_identity)
++goa_kerberos_identity_refresh (GoaKerberosIdentity *self)
+ {
+ VerificationLevel old_verification_level, new_verification_level;
+- gboolean should_set_cache_active = FALSE;
+- gboolean time_changed = FALSE;
+- char *preauth_identity_source = NULL;
+- g_autofree char *default_principal = NULL;
+- int comparison;
++ g_autofree char *preauth_identity_source = NULL;
++ g_autoptr (GError) error = NULL;
+
+- G_LOCK (identity_lock);
+-
+- g_debug ("GoaKerberosIdentity: Evaluating updated credentials for identity %s "
+- "(old credentials cache name: %s, new credentials cache name: %s)",
++ g_debug ("GoaKerberosIdentity: Refreshing identity %s (active credentials cache: %s)",
+ self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- old_verification_level = self->cached_verification_level;
+- new_verification_level = new_identity->cached_verification_level;
+-
+- default_principal = get_default_principal (self);
+- comparison = goa_kerberos_identity_compare (self, new_identity);
+-
+- if (new_identity->active_credentials_cache_name != NULL)
+- {
+- g_autofree char *default_cache_name = NULL;
+- krb5_ccache credentials_cache;
+- krb5_ccache copied_cache;
+- gboolean should_set_cache_as_default = FALSE;
+- gboolean is_default_principal = FALSE, is_default_cache = FALSE;
+- gboolean cache_already_active = FALSE;
+-
+- is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+-
+- default_cache_name = get_default_cache_name (self);
+- is_default_cache = g_strcmp0 (default_cache_name, self->active_credentials_cache_name) == 0;
+- cache_already_active = g_strcmp0 (self->active_credentials_cache_name, new_identity->active_credentials_cache_name) == 0;
+-
+- g_debug ("GoaKerberosIdentity: Default credentials cache is '%s' (is %sus, is %sactive)", default_cache_name, is_default_cache? "" : "not ", cache_already_active? "" : "not ");
+-
+- if (default_principal == NULL)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Setting default credentials cache to '%s' (principal %s) "
+- "because there is no active default",
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- else if (!is_default_principal)
+- {
+- g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' (principal %s) "
+- "because identity is currently not default (credentials already active? %s)",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- cache_already_active? "yes" : "no");
+- should_set_cache_as_default = FALSE;
+-
+- if (comparison < 0)
+- {
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to credentials cache '%s' "
+- "because it has better credentials",
+- self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching identity %s from credentials cache '%s' to "
+- "'%s' because it is newer and is otherwise just as good",
+- self->identifier,
+- self->active_credentials_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- else
+- {
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not switching identity %s from credentials cache '%s' to '%s' "
+- "because it has less good credentials",
+- self->identifier,
+- default_cache_name,
+- new_identity->active_credentials_cache_name);
+- }
+- }
+- else if (cache_already_active)
+- {
+- if (is_default_cache)
+- {
+- should_set_cache_as_default = FALSE;
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not setting default credentials cache to '%s' "
+- "because cache is already active for identity %s and identity is default",
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- else
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default and cache is supposed to be active already but isn't",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier);
+- }
+- }
+- else
+- {
+- if (comparison < 0)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default and the cache has better credentials than those "
+- "in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- else if (comparison == 0 && old_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- should_set_cache_as_default = TRUE;
+- should_set_cache_active = TRUE;
+-
+- g_debug ("GoaKerberosIdentity: Switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default, and the cache has newer, and otherwise "
+- "just as good credentials as those in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- else
+- {
+- should_set_cache_as_default = FALSE;
+- should_set_cache_active = FALSE;
+-
+- g_debug ("GoaKerberosIdentity: Not switching default credentials cache from '%s' to '%s' "
+- "because identity %s is default but newer credentials aren't as good as those in '%s'",
+- default_cache_name,
+- new_identity->active_credentials_cache_name,
+- self->identifier,
+- self->active_credentials_cache_name);
+- }
+- }
+- credentials_cache = (krb5_ccache) g_hash_table_lookup (new_identity->credentials_caches,
+- new_identity->active_credentials_cache_name);
+- krb5_cc_dup (new_identity->kerberos_context, credentials_cache, &copied_cache);
+-
+- if (should_set_cache_active)
+- {
+- g_clear_pointer (&self->active_credentials_cache_name, g_free);
+- self->active_credentials_cache_name = g_strdup (new_identity->active_credentials_cache_name);
+- }
++ self->active_credentials_cache_name);
+
+- goa_kerberos_identity_add_credentials_cache (self, copied_cache);
+-
+- if (should_set_cache_as_default)
+- krb5_cc_switch (self->kerberos_context, copied_cache);
+- }
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
+ G_UNLOCK (identity_lock);
+
+- clear_alarms (new_identity);
+-
+- if (!should_set_cache_active)
+- return;
++ new_verification_level = verify_identity (self, &preauth_identity_source, &error);
+
+ G_LOCK (identity_lock);
+- g_debug ("GoaKerberosIdentity: Setting identity %s to use updated credentials in credentials cache '%s'",
+- self->identifier, self->active_credentials_cache_name);
+- update_identifier (self, new_identity);
+- time_changed |= set_start_time (self, new_identity->start_time);
+- time_changed |= set_renewal_time (self, new_identity->renewal_time);
+- time_changed |= set_expiration_time (self, new_identity->expiration_time);
+- G_UNLOCK (identity_lock);
+-
+- if (time_changed)
+- {
+- if (new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
+- reset_alarms (self);
+- }
+- else
+- {
+- g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
+- clear_alarms (self);
+- }
+- }
+- else
++ if (g_strcmp0 (self->preauth_identity_source, preauth_identity_source) != 0)
+ {
+- g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
++ g_free (self->preauth_identity_source);
++ self->preauth_identity_source = g_steal_pointer (&preauth_identity_source);
+ }
+-
+- G_LOCK (identity_lock);
+- g_free (self->preauth_identity_source);
+- self->preauth_identity_source = preauth_identity_source;
+ G_UNLOCK (identity_lock);
+
+ if (new_verification_level != old_verification_level)
+ {
+- if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
++ if ((old_verification_level == VERIFICATION_LEVEL_SIGNED_IN) &&
+ new_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+ else
+ {
+ G_LOCK (identity_lock);
+ self->cached_verification_level = new_verification_level;
+ G_UNLOCK (identity_lock);
+ }
++ G_LOCK (identity_lock);
+ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ G_UNLOCK (identity_lock);
+ }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+@@ -1945,47 +1915,45 @@ goa_kerberos_identity_erase (GoaKerberosIdentity *self, GError **error)
+ g_debug ("GoaKerberosIdentity: Destroying active credentials cache %s", self->active_credentials_cache_name);
+ error_code = krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_REMOVING_CREDENTIALS,
+ error_code, _("Could not erase identity: "));
+ }
+ }
+
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ g_debug ("GoaKerberosIdentity: Destroying inactive credentials cache %s", name);
+ krb5_cc_destroy (self->kerberos_context, credentials_cache);
+ }
+ g_hash_table_remove_all (self->credentials_caches);
+
+ return error_code == 0;
+ }
+
+ GoaIdentity *
+ goa_kerberos_identity_new (krb5_context context, krb5_ccache cache, GError **error)
+ {
+ GoaKerberosIdentity *self;
+- krb5_ccache copied_cache;
+
+ self = GOA_KERBEROS_IDENTITY (g_object_new (GOA_TYPE_KERBEROS_IDENTITY, NULL));
+ self->kerberos_context = context;
+
+- krb5_cc_dup (self->kerberos_context, cache, &copied_cache);
+- goa_kerberos_identity_add_credentials_cache (self, copied_cache);
++ goa_kerberos_identity_add_credentials_cache (self, cache);
+
+ error = NULL;
+ if (!g_initable_init (G_INITABLE (self), NULL, error))
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+
+ return GOA_IDENTITY (self);
+ }
+diff --git a/src/goaidentity/goakerberosidentity.h b/src/goaidentity/goakerberosidentity.h
+index de0752cd..70cd4e3f 100644
+--- a/src/goaidentity/goakerberosidentity.h
++++ b/src/goaidentity/goakerberosidentity.h
+@@ -14,54 +14,58 @@
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+ #ifndef __GOA_KERBEROS_IDENTITY_H__
+ #define __GOA_KERBEROS_IDENTITY_H__
+
+ #include <glib.h>
+ #include <glib-object.h>
+
+ #include <krb5.h>
+ #include "goaidentityinquiry.h"
+
+ G_BEGIN_DECLS
+
+ #define GOA_TYPE_KERBEROS_IDENTITY (goa_kerberos_identity_get_type ())
+ G_DECLARE_FINAL_TYPE (GoaKerberosIdentity, goa_kerberos_identity, GOA, KERBEROS_IDENTITY, GObject);
+
+ typedef enum
+ {
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_REALM,
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_AND_REALM,
+ GOA_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_ROLE_AND_REALM
+ } GoaKerberosIdentityDescriptionLevel;
+
+ GoaIdentity *goa_kerberos_identity_new (krb5_context kerberos_context,
+ krb5_ccache cache,
+ GError **error);
+
++gboolean goa_kerberos_identity_has_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache credentials_cache);
++void goa_kerberos_identity_add_credentials_cache (GoaKerberosIdentity *self,
++ krb5_ccache cache);
++
+ gboolean goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ const char *principal_name,
+ gconstpointer initial_password,
+ const char *preauth_source,
+ GoaIdentitySignInFlags flags,
+ GoaIdentityInquiryFunc inquiry_func,
+ gpointer inquiry_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error);
+-void goa_kerberos_identity_update (GoaKerberosIdentity *identity,
+- GoaKerberosIdentity *new_identity);
++void goa_kerberos_identity_refresh (GoaKerberosIdentity *identity);
+ gboolean goa_kerberos_identity_renew (GoaKerberosIdentity *self,
+ GError **error);
+ gboolean goa_kerberos_identity_erase (GoaKerberosIdentity *self,
+ GError **error);
+
+ char *goa_kerberos_identity_get_principal_name (GoaKerberosIdentity *self);
+ char *goa_kerberos_identity_get_realm_name (GoaKerberosIdentity *self);
+ char *goa_kerberos_identity_get_preauthentication_source (GoaKerberosIdentity *self);
+
+ G_END_DECLS
+
+ #endif /* __GOA_KERBEROS_IDENTITY_H__ */
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index d4ff2de4..7785b891 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -471,207 +471,227 @@ static void
+ drop_stale_identities (GoaKerberosIdentityManager *self,
+ Operation *operation,
+ GHashTable *known_identities)
+ {
+ GList *stale_identity_ids;
+ GList *node;
+
+ stale_identity_ids = g_hash_table_get_keys (self->identities);
+
+ node = stale_identity_ids;
+ while (node != NULL)
+ {
+ GoaIdentity *identity;
+ const char *identifier = node->data;
+
+ identity = g_hash_table_lookup (known_identities, identifier);
+ if (identity == NULL)
+ {
+ identity = g_hash_table_lookup (self->identities, identifier);
+
+ if (identity != NULL)
+ {
+ remove_identity (self, operation, identity);
+ }
+ }
+ node = node->next;
+ }
+ g_list_free (stale_identity_ids);
+ }
+
+-static void
+-update_identity (GoaKerberosIdentityManager *self,
+- Operation *operation,
+- GoaIdentity *identity,
+- GoaIdentity *new_identity)
+-{
+-
+- goa_kerberos_identity_update (GOA_KERBEROS_IDENTITY (identity),
+- GOA_KERBEROS_IDENTITY (new_identity));
+-
+- if (goa_identity_is_signed_in (identity))
+- {
+- IdentitySignalWork *work;
+-
+- /* if it's not expired, send out a refresh signal */
+- g_debug ("GoaKerberosIdentityManager: identity '%s' refreshed",
+- goa_identity_get_identifier (identity));
+-
+- work = identity_signal_work_new (self, identity);
+- goa_kerberos_identify_manager_send_to_context (operation->context,
+- (GSourceFunc)
+- do_identity_signal_refreshed_work,
+- work,
+- (GDestroyNotify)
+- identity_signal_work_free);
+- }
+-}
+-
+ static void
+ add_identity (GoaKerberosIdentityManager *self,
+ Operation *operation,
+ GoaIdentity *identity,
+ const char *identifier)
+ {
+ IdentitySignalWork *work;
+
+ g_hash_table_replace (self->identities, g_strdup (identifier), g_object_ref (identity));
+
+ if (!goa_identity_is_signed_in (identity))
+ g_hash_table_replace (self->expired_identities, g_strdup (identifier), identity);
+
+ work = identity_signal_work_new (self, identity);
+ goa_kerberos_identify_manager_send_to_context (operation->context,
+ (GSourceFunc)
+ do_identity_signal_added_work,
+ work,
+ (GDestroyNotify) identity_signal_work_free);
+ }
+
++static char *
++get_principal_from_cache (GoaKerberosIdentityManager *self,
++ krb5_ccache cache)
++{
++ int error_code;
++ krb5_principal principal;
++ char *unparsed_name;
++ char *principal_name;
++
++ error_code = krb5_cc_get_principal (self->kerberos_context, cache, &principal);
++
++ error_code = krb5_unparse_name_flags (self->kerberos_context, principal, 0, &unparsed_name);
++ krb5_free_principal (self->kerberos_context, principal);
++
++ if (error_code != 0)
++ return NULL;
++
++ principal_name = g_strdup (unparsed_name);
++
++ krb5_free_unparsed_name (self->kerberos_context, unparsed_name);
++
++ return principal_name;
++}
++
+ static void
+-refresh_identity (GoaKerberosIdentityManager *self,
+- Operation *operation,
+- GHashTable *refreshed_identities,
+- GoaIdentity *identity)
++import_credentials_cache (GoaKerberosIdentityManager *self,
++ Operation *operation,
++ GHashTable *refreshed_identities,
++ krb5_ccache cache)
+ {
+- const char *identifier;
+- GoaIdentity *old_identity;
++ g_autofree char *identifier = NULL;
++ GoaIdentity *identity = NULL;
+
+- identifier = goa_identity_get_identifier (identity);
++ identifier = get_principal_from_cache (self, cache);
+
+ if (identifier == NULL)
+ return;
+
+- old_identity = g_hash_table_lookup (self->identities, identifier);
++ identity = g_hash_table_lookup (self->identities, identifier);
+
+- if (old_identity != NULL)
++ if (identity == NULL)
+ {
+- g_debug ("GoaKerberosIdentityManager: refreshing identity '%s'", identifier);
+- update_identity (self, operation, old_identity, identity);
++ g_autoptr(GError) error = NULL;
+
+- /* Reuse the old identity, so any object data set up on it doesn't
+- * disappear spurriously
+- */
+- identifier = goa_identity_get_identifier (old_identity);
+- identity = old_identity;
++ g_debug ("GoaKerberosIdentityManager: Adding new identity '%s'", identifier);
++ identity = goa_kerberos_identity_new (self->kerberos_context, cache, &error);
++
++ if (error != NULL)
++ {
++ g_debug ("GoaKerberosIdentityManager: Could not track identity %s: %s",
++ identifier, error->message);
++ return;
++ }
++
++ add_identity (self, operation, identity, identifier);
+ }
+ else
+ {
+- g_debug ("GoaKerberosIdentityManager: adding new identity '%s'", identifier);
+- add_identity (self, operation, identity, identifier);
++ if (!goa_kerberos_identity_has_credentials_cache (GOA_KERBEROS_IDENTITY (identity), cache))
++ goa_kerberos_identity_add_credentials_cache (GOA_KERBEROS_IDENTITY (identity), cache);
+ }
+
+- /* Track refreshed identities so we can emit removals when we're done fully
++ /* Track refreshed identities so we can emit refreshes and removals when we're done fully
+ * enumerating the collection of credential caches
+ */
+ g_hash_table_replace (refreshed_identities,
+ g_strdup (identifier),
+ g_object_ref (identity));
+ }
+
+ static gboolean
+ refresh_identities (GoaKerberosIdentityManager *self,
+ Operation *operation)
+ {
+ krb5_error_code error_code;
+ krb5_ccache cache;
+ krb5_cccol_cursor cursor;
+ const char *error_message;
+ GHashTable *refreshed_identities;
++ GHashTableIter iter;
++ const char *name;
++ GoaIdentity *identity;
+
+ /* If we have more refreshes queued up, don't bother doing this one
+ */
+ if (!g_atomic_int_dec_and_test (&self->pending_refresh_count))
+ {
+ return FALSE;
+ }
+
+ g_debug ("GoaKerberosIdentityManager: Refreshing identities");
+ refreshed_identities = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ error_code = krb5_cccol_cursor_new (self->kerberos_context, &cursor);
+
+ if (error_code != 0)
+ {
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentityManager: Error looking up available credential caches: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ goto done;
+ }
+
+ error_code = krb5_cccol_cursor_next (self->kerberos_context, cursor, &cache);
+
+ while (error_code == 0 && cache != NULL)
+ {
+- GoaIdentity *identity;
+-
+- identity = goa_kerberos_identity_new (self->kerberos_context, cache, NULL);
+-
+- if (identity != NULL)
+- {
+- refresh_identity (self, operation, refreshed_identities, identity);
+- g_object_unref (identity);
+- }
++ import_credentials_cache (self, operation, refreshed_identities, cache);
+
+ krb5_cc_close (self->kerberos_context, cache);
+ error_code = krb5_cccol_cursor_next (self->kerberos_context, cursor, &cache);
+ }
+
+ if (error_code != 0)
+ {
+ error_message = krb5_get_error_message (self->kerberos_context, error_code);
+ g_debug ("GoaKerberosIdentityManager: Error iterating over available credential caches: %s",
+ error_message);
+ krb5_free_error_message (self->kerberos_context, error_message);
+ }
+
+ krb5_cccol_cursor_free (self->kerberos_context, &cursor);
++
++ g_hash_table_iter_init (&iter, self->identities);
++ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &identity))
++ {
++ goa_kerberos_identity_refresh (GOA_KERBEROS_IDENTITY (identity));
++
++ if (goa_identity_is_signed_in (identity))
++ {
++ IdentitySignalWork *work;
++
++ /* if it's not expired, send out a refresh signal */
++ g_debug ("GoaKerberosIdentityManager: identity '%s' refreshed",
++ goa_identity_get_identifier (identity));
++
++ work = identity_signal_work_new (self, identity);
++ goa_kerberos_identify_manager_send_to_context (operation->context,
++ (GSourceFunc)
++ do_identity_signal_refreshed_work,
++ work,
++ (GDestroyNotify)
++ identity_signal_work_free);
++ }
++ }
++
+ done:
+ drop_stale_identities (self, operation, refreshed_identities);
+ g_hash_table_unref (refreshed_identities);
+
+ return TRUE;
+ }
+
+ static int
+ identity_sort_func (GoaIdentity *a,
+ GoaIdentity *b)
+ {
+ return g_strcmp0 (goa_identity_get_identifier (a),
+ goa_identity_get_identifier (b));
+ }
+
+ static void
+ free_identity_list (GList *list)
+ {
+ g_list_free_full (list, g_object_unref);
+ }
+
+ static void
+ list_identities (GoaKerberosIdentityManager *self,
+ Operation *operation)
+ {
+ GList *identities;
+
+ g_debug ("GoaKerberosIdentityManager: Listing identities");
+ identities = g_hash_table_get_values (self->identities);
+
+--
+2.39.3
+
+
+From 7b78149d1f402412ea73b587c141e3f0ec8e87a3 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Thu, 9 Feb 2023 12:05:16 -0500
+Subject: [PATCH 17/22] goakerberosidentity: Ensure credentials of expired
+ identities at startup
+
+If the identity service is started later than goa-daemon then it won't
+currently notify goa-daemon about tickets that expired before it
+was running.
+
+This commit fixes the problem by manually calling EnsureCredentials on
+all signed out identities after their initial enumeration.
+---
+ src/goaidentity/goaidentityservice.c | 20 ++++++++++++++++----
+ 1 file changed, 16 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goaidentityservice.c b/src/goaidentity/goaidentityservice.c
+index a25de416..7c2e389b 100644
+--- a/src/goaidentity/goaidentityservice.c
++++ b/src/goaidentity/goaidentityservice.c
+@@ -1442,82 +1442,88 @@ sign_in (GoaIdentityService *self,
+ on_identity_inquiry,
+ self,
+ cancellable,
+ (GAsyncReadyCallback)
+ on_identity_signed_in,
+ g_object_ref (operation_result));
+
+ g_object_unref (operation_result);
+ }
+
+ static void
+ on_identity_expiring (GoaIdentityManager *identity_manager,
+ GoaIdentity *identity,
+ GoaIdentityService *self)
+ {
+ const char *principal;
+ GoaObject *object;
+
+ principal = goa_identity_get_identifier (identity);
+
+ g_debug ("GoaIdentityService: identity %s expiring", principal);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ return;
+
+ ensure_account_credentials (self, object);
+ g_clear_object (&object);
+ }
+-
+ static void
+-on_identity_expired (GoaIdentityManager *identity_manager,
+- GoaIdentity *identity,
+- GoaIdentityService *self)
++handle_identity_expired (GoaIdentityService *self,
++ GoaIdentity *identity)
+ {
+ const char *principal;
+ GoaObject *object;
+
+ principal = goa_identity_get_identifier (identity);
+
+ g_debug ("GoaIdentityService: identity %s expired", principal);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ return;
+
+ ensure_account_credentials (self, object);
+ g_clear_object (&object);
+ }
+
++static void
++on_identity_expired (GoaIdentityManager *identity_manager,
++ GoaIdentity *identity,
++ GoaIdentityService *self)
++{
++ handle_identity_expired (self, identity);
++}
++
+ static void
+ on_sign_out_for_account_change_done (GoaIdentityService *self,
+ GAsyncResult *result)
+ {
+ GError *error = NULL;
+ gboolean had_error;
+
+ /* Workaround for bgo#764163 */
+ had_error = g_task_had_error (G_TASK (result));
+ g_task_propagate_boolean (G_TASK (result), &error);
+ if (had_error)
+ {
+ g_debug ("Log out failed: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_debug ("Log out complete");
+ }
+ }
+
+ static void
+ on_ticketing_done (GoaIdentityService *self,
+ GAsyncResult *result)
+ {
+ GoaObject *object;
+
+ object = g_task_get_task_data (G_TASK (result));
+ ensure_account_credentials (self, object);
+ }
+@@ -1678,60 +1684,66 @@ on_identities_listed (GoaIdentityManager *manager,
+
+ if (identities == NULL)
+ {
+ if (error != NULL)
+ {
+ g_warning ("Could not list identities: %s", error->message);
+ g_error_free (error);
+ }
+ goto out;
+ }
+
+ for (node = identities; node != NULL; node = node->next)
+ {
+ GoaIdentity *identity = node->data;
+ const char *principal;
+ GoaObject *object;
+ char *object_path;
+
+ object_path = export_identity (self, identity);
+
+ principal = goa_identity_get_identifier (identity);
+
+ object = find_object_with_principal (self, principal, TRUE);
+
+ if (object == NULL)
+ add_temporary_account (self, identity);
+ else
+ g_object_unref (object);
+
+ g_free (object_path);
++
++ /* Treat identities that started out expired as if they just expired, in case
++ * the identity service is started long after goa-daemon
++ */
++ if (!goa_identity_is_signed_in (identity))
++ handle_identity_expired (self, identity);
+ }
+
+ out:
+ g_object_unref (self);
+ }
+
+ static void
+ ensure_credentials_for_accounts (GoaIdentityService *self)
+ {
+ GDBusObjectManager *object_manager;
+ GList *accounts;
+ GList *node;
+
+ object_manager = goa_client_get_object_manager (self->client);
+
+ g_signal_connect (object_manager, "interface-added", G_CALLBACK (on_account_interface_added), self);
+ g_signal_connect (object_manager, "interface-removed", G_CALLBACK (on_account_interface_removed), self);
+
+ accounts = goa_client_get_accounts (self->client);
+
+ for (node = accounts; node != NULL; node = node->next)
+ {
+ GoaObject *object = GOA_OBJECT (node->data);
+ GoaAccount *account;
+ GoaTicketing *ticketing;
+ const char *provider_type;
+
+ account = goa_object_peek_account (object);
+
+ if (account == NULL)
+--
+2.39.3
+
+
+From 4003fdbed262f2da2e0affb39bda68c7ae1ccf18 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Tue, 21 Feb 2023 12:10:46 -0500
+Subject: [PATCH 18/22] goakerberosidentity: Don't send "expired" and
+ "unexpired" signals from two parts of the code
+
+Since commit e869642bd079aec2098542a3c8f1b296694499ab verify_identity
+handles sending "expired" and "unexpired" signals on its own.
+
+Unfortunately that commit neglected to make
+goa_kerberos_identity_refresh stop sending the signals.
+
+This commit removes the duplicated logic.
+---
+ src/goaidentity/goakerberosidentity.c | 42 ++++-----------------------
+ 1 file changed, 6 insertions(+), 36 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index b5cbcecd..20add727 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1753,112 +1753,82 @@ goa_kerberos_identity_sign_in (GoaKerberosIdentity *self,
+ destroy_notify (inquiry_data);
+ sign_in_operation_free (operation);
+
+ if (!goa_kerberos_identity_update_credentials (self,
+ principal,
+ &new_credentials,
+ error))
+ {
+ krb5_free_principal (self->kerberos_context, principal);
+ goto done;
+ }
+ krb5_free_principal (self->kerberos_context, principal);
+
+ done:
+
+ goa_kerberos_identity_refresh (self);
+
+ if (self->cached_verification_level != VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ g_debug ("GoaKerberosIdentity: Identity '%s' could not be signed in", principal_name);
+ return FALSE;
+ }
+
+ g_debug ("GoaKerberosIdentity: Identity '%s' signed in", principal_name);
+ return TRUE;
+ }
+
+ void
+ goa_kerberos_identity_refresh (GoaKerberosIdentity *self)
+ {
+- VerificationLevel old_verification_level, new_verification_level;
+ g_autofree char *preauth_identity_source = NULL;
+ g_autoptr (GError) error = NULL;
+
+ g_debug ("GoaKerberosIdentity: Refreshing identity %s (active credentials cache: %s)",
+ self->identifier,
+ self->active_credentials_cache_name);
+
+- G_LOCK (identity_lock);
+- old_verification_level = self->cached_verification_level;
+- G_UNLOCK (identity_lock);
++ verify_identity (self, &preauth_identity_source, &error);
+
+- new_verification_level = verify_identity (self, &preauth_identity_source, &error);
++ if (error != NULL)
++ {
++ g_debug ("GoaKerberosIdentity: Could not verify identity %s: %s", self->identifier, error->message);
++ return;
++ }
+
+ G_LOCK (identity_lock);
+ if (g_strcmp0 (self->preauth_identity_source, preauth_identity_source) != 0)
+ {
+ g_free (self->preauth_identity_source);
+ self->preauth_identity_source = g_steal_pointer (&preauth_identity_source);
+ }
+ G_UNLOCK (identity_lock);
+-
+- if (new_verification_level != old_verification_level)
+- {
+- if ((old_verification_level == VERIFICATION_LEVEL_SIGNED_IN) &&
+- new_verification_level == VERIFICATION_LEVEL_EXISTS)
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+-
+- g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+- }
+- else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+- new_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+-
+- g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+- }
+- else
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = new_verification_level;
+- G_UNLOCK (identity_lock);
+- }
+- G_LOCK (identity_lock);
+- queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+- G_UNLOCK (identity_lock);
+- }
+ }
+
+ gboolean
+ goa_kerberos_identity_renew (GoaKerberosIdentity *self, GError **error)
+ {
+ krb5_error_code error_code = 0;
+ krb5_principal principal;
+ krb5_creds new_credentials;
+ krb5_ccache credentials_cache;
+ gboolean renewed = FALSE;
+ char *name = NULL;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_set_error (error,
+ GOA_IDENTITY_ERROR,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ _("Not signed in"));
+ goto out;
+ }
+
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ error_code = krb5_cc_get_principal (self->kerberos_context, credentials_cache, &principal);
+ if (error_code != 0)
+ {
+ set_and_prefix_error_from_krb5_error_code (self,
+ error,
+ GOA_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE,
+ error_code,
+--
+2.39.3
+
+
+From 967384abe9044cfeacaf99f5df2c2985d3b2e357 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 20 Feb 2023 14:30:24 -0500
+Subject: [PATCH 19/22] goakerberosidentity: Ensure properties are updated in a
+ timely fashion
+
+At the moment property notifications of identity objects are deferred
+to a lower priority idle handler.
+
+This is suboptimal because it means there can be a bit of a delay
+updating the status of, e.g., IsSignedIn, over the bus.
+
+This commit changes the notification to queue at normal priority.
+---
+ src/goaidentity/goakerberosidentity.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 20add727..de646d5f 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -549,61 +549,61 @@ clear_idle_id (NotifyRequest *request)
+
+ g_object_unref (request->self);
+ g_slice_free (NotifyRequest, request);
+ }
+
+ static gboolean
+ on_notify_queued (NotifyRequest *request)
+ {
+ g_object_notify (G_OBJECT (request->self), request->property_name);
+
+ return FALSE;
+ }
+
+ static void
+ queue_notify (GoaKerberosIdentity *self,
+ guint *idle_id,
+ const char *property_name)
+ {
+ NotifyRequest *request;
+
+ if (*idle_id != 0)
+ {
+ return;
+ }
+
+ request = g_slice_new0 (NotifyRequest);
+ request->self = g_object_ref (self);
+ request->idle_id = idle_id;
+ request->property_name = property_name;
+
+- *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
++ *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+ (GSourceFunc)
+ on_notify_queued,
+ request,
+ (GDestroyNotify)
+ clear_idle_id);
+ }
+
+ static gboolean
+ set_start_time (GoaKerberosIdentity *self,
+ krb5_timestamp start_time)
+ {
+ if (self->start_time != start_time)
+ {
+ self->start_time = start_time;
+ queue_notify (self, &self->start_time_idle_id, "start-timestamp");
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ static gboolean
+ set_renewal_time (GoaKerberosIdentity *self,
+ krb5_timestamp renewal_time)
+ {
+ if (self->renewal_time != renewal_time)
+ {
+ self->renewal_time = renewal_time;
+ queue_notify (self, &self->renewal_time_idle_id, "renewal-timestamp");
+ return TRUE;
+ }
+--
+2.39.3
+
+
+From 345403d4842005b6666fb059a32da8701964f95d Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 20 Feb 2023 14:32:52 -0500
+Subject: [PATCH 20/22] goakerberosidentity: Queue is-signed-in notify before
+ emitting expired signal
+
+Right now the "expired" signal is emitted before the "is-signed-in"
+property notification is queued. This notification is used to update the
+"IsSignedIn" property of the object on the bus. The "expired" signal
+emission leads to a corresponding "identity-expired" signal on the manager
+object. A handler for that signal calls into gnome-online-accounts to check
+identity status. In response, gnome-online-accounts will check the "IsSignedIn"
+property.
+
+This commit makes sure the "is-signed-in" notification is queued first,
+before the "expired" signal is emitted.
+
+Of course queuing the notify before "expired" is emitted isn't enough to
+ensure the notify happens in time. A future commit will make sure the
+"identity-expired" signal itself is deferred to the main thread, which
+should resolve that problem.
+---
+ src/goaidentity/goakerberosidentity.c | 20 +++++---------------
+ 1 file changed, 5 insertions(+), 15 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index de646d5f..0eb0a9ca 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -1028,85 +1028,75 @@ out:
+ time_changed |= set_renewal_time (self, best_renewal_time);
+ time_changed |= set_expiration_time (self, best_expiration_time);
+ G_UNLOCK (identity_lock);
+
+ if (time_changed)
+ {
+ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials have updated times, resetting alarms", self->identifier);
+ reset_alarms (self);
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials are now expired, clearing alarms", self->identifier);
+ clear_alarms (self);
+ }
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity %s credentials do not have updated times, so not adjusting alarms", self->identifier);
+ }
+ }
+ else
+ {
+ g_debug ("GoaKerberosIdentity: identity is unverified, clearing alarms");
+ clear_alarms (self);
+ }
+
+ if (best_verification_level != old_verification_level)
+ {
++ G_LOCK (identity_lock);
++ self->cached_verification_level = best_verification_level;
++ queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
++ G_UNLOCK (identity_lock);
++
+ if (old_verification_level == VERIFICATION_LEVEL_SIGNED_IN &&
+ best_verification_level == VERIFICATION_LEVEL_EXISTS)
+ {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0);
+ }
+ else if (old_verification_level == VERIFICATION_LEVEL_EXISTS &&
+ best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0);
+ }
+- else
+- {
+- G_LOCK (identity_lock);
+- self->cached_verification_level = best_verification_level;
+- G_UNLOCK (identity_lock);
+- }
+- queue_notify (self, &self->is_signed_in_idle_id, "is-signed-in");
+ }
+
+ default_principal = get_default_principal (self);
+ is_default_principal = g_strcmp0 (default_principal, self->identifier) == 0;
+
+ default_credentials_cache_name = get_default_cache_name (self);
+ is_default_credentials_cache = g_strcmp0 (default_credentials_cache_name, self->active_credentials_cache_name) == 0;
+
+ if (self->active_credentials_cache_name == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s has no active credentials cache to switch to", self->identifier);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (self->identifier == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache to '%s' because it is not yet initialized", self->active_credentials_cache_name);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (default_principal == NULL)
+ {
+ g_debug ("GoaKerberosIdentity: Switching default credentials cache to '%s' (identity %s) because there is currently no default", self->active_credentials_cache_name, self->identifier);
+ should_switch_default_credentials_cache = TRUE;
+ }
+ else if (!is_default_principal)
+ {
+ g_debug ("GoaKerberosIdentity: Not switching default credentials cache because identity %s is not the default identity", self->identifier);
+ should_switch_default_credentials_cache = FALSE;
+ }
+ else if (!is_default_credentials_cache)
+ {
+--
+2.39.3
+
+
+From 3a3a9fbbb84adc7c8797027a2c8c9b3fada03e0b Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Mon, 20 Feb 2023 14:57:57 -0500
+Subject: [PATCH 21/22] goakerberosidentitymanager: Ensure identity-expired
+ signal is emitted from main loop thread
+
+Right now most of the the identity manager signals get emitted from the
+main thread. This makes sense, and is what a caller would typically
+expect (given they connect their callbacks in the main thread as well).
+
+The one exception is "identity-expired" which gets emitted from the
+worker thread. This commit fixes that.
+---
+ src/goaidentity/goakerberosidentitymanager.c | 21 ++++++++++++++++++--
+ 1 file changed, 19 insertions(+), 2 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
+index 7785b891..9a89917e 100644
+--- a/src/goaidentity/goakerberosidentitymanager.c
++++ b/src/goaidentity/goakerberosidentitymanager.c
+@@ -254,66 +254,83 @@ static void
+ schedule_refresh (GoaKerberosIdentityManager *self)
+ {
+ Operation *operation;
+
+ g_atomic_int_inc (&self->pending_refresh_count);
+
+ operation = operation_new (self, NULL, OPERATION_TYPE_REFRESH, NULL);
+ g_thread_pool_push (self->thread_pool, operation, NULL);
+ }
+
+ static IdentitySignalWork *
+ identity_signal_work_new (GoaKerberosIdentityManager *self,
+ GoaIdentity *identity)
+ {
+ IdentitySignalWork *work;
+
+ work = g_slice_new (IdentitySignalWork);
+ work->manager = self;
+ work->identity = g_object_ref (identity);
+
+ return work;
+ }
+
+ static void
+ identity_signal_work_free (IdentitySignalWork *work)
+ {
+ g_object_unref (work->identity);
+ g_slice_free (IdentitySignalWork, work);
+ }
+
++static void
++do_identity_signal_expired_work (IdentitySignalWork *work)
++{
++ GoaKerberosIdentityManager *self = work->manager;
++ GoaIdentity *identity = work->identity;
++
++ g_debug ("GoaKerberosIdentityManager: identity expired");
++ _goa_identity_manager_emit_identity_expired (GOA_IDENTITY_MANAGER (self), identity);
++}
++
+ static void
+ on_identity_expired (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+- _goa_identity_manager_emit_identity_expired (GOA_IDENTITY_MANAGER (self),
+- identity);
++ IdentitySignalWork *work;
++
++ work = identity_signal_work_new (self, identity);
++ goa_kerberos_identify_manager_send_to_context (g_main_context_default (),
++ (GSourceFunc)
++ do_identity_signal_expired_work,
++ work,
++ (GDestroyNotify)
++ identity_signal_work_free);
+ }
+
+ static void
+ on_identity_unexpired (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity unexpired");
+ /* If an identity is now unexpired, that means some sort of weird
+ * clock skew happened and we should just do a full refresh, since it's
+ * probably affected more than one identity
+ */
+ schedule_refresh (self);
+ }
+
+ static void
+ on_identity_expiring (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity about to expire");
+ _goa_identity_manager_emit_identity_expiring (GOA_IDENTITY_MANAGER (self),
+ identity);
+ }
+
+ static void
+ on_identity_needs_renewal (GoaIdentity *identity,
+ GoaKerberosIdentityManager *self)
+ {
+ g_debug ("GoaKerberosIdentityManager: identity needs renewal");
+ _goa_identity_manager_emit_identity_needs_renewal (GOA_IDENTITY_MANAGER (self),
+ identity);
+--
+2.39.3
+
+
+From 792600480e19f9e973fd7261fa5973be836f8470 Mon Sep 17 00:00:00 2001
+From: Ray Strode <rstrode@redhat.com>
+Date: Tue, 21 Feb 2023 12:07:04 -0500
+Subject: [PATCH 22/22] goakerberosidentity: Ensure old_verification_level is
+ initialized
+
+verify_identity has a short-circuit at the top of the file if the
+active credentials cache is already known. In that case it bypasses
+the search for a credentials cache. Unfortunately it also inadvertently
+bypasses initialization of old_verification_level, leading to the
+code thinking the identity is always going unexpired.
+
+This commit fixes that.
+---
+ src/goaidentity/goakerberosidentity.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/goaidentity/goakerberosidentity.c b/src/goaidentity/goakerberosidentity.c
+index 0eb0a9ca..e4f09e14 100644
+--- a/src/goaidentity/goakerberosidentity.c
++++ b/src/goaidentity/goakerberosidentity.c
+@@ -859,101 +859,101 @@ get_default_cache_name (GoaKerberosIdentity *self)
+ krb5_free_unparsed_name (self->kerberos_context, principal_name);
+
+ default_cache_name = g_strdup (krb5_cc_get_name (self->kerberos_context, default_cache));
+ krb5_cc_close (self->kerberos_context, default_cache);
+
+ return default_cache_name;
+ }
+
+ static VerificationLevel
+ verify_identity (GoaKerberosIdentity *self,
+ char **preauth_identity_source,
+ GError **error)
+ {
+ krb5_ccache credentials_cache;
+ g_autofree char *default_principal = NULL;
+ g_autofree char *default_credentials_cache_name = NULL;
+ gboolean is_default_principal;
+ gboolean is_default_credentials_cache;
+ gboolean should_switch_default_credentials_cache = FALSE;
+ gboolean time_changed = FALSE;
+ const char *name;
+ krb5_timestamp best_start_time = 0;
+ krb5_timestamp best_renewal_time = 0;
+ krb5_timestamp best_expiration_time = 0;
+ g_autofree char *best_preauth_identity_source = NULL;
+ g_autofree char *best_credentials_cache_name = NULL;
+ VerificationLevel old_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ VerificationLevel best_verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ GHashTableIter iter;
+
++ G_LOCK (identity_lock);
++ old_verification_level = self->cached_verification_level;
++ G_UNLOCK (identity_lock);
++
+ if (self->active_credentials_cache_name != NULL)
+ {
+ G_LOCK (identity_lock);
+ credentials_cache = (krb5_ccache) g_hash_table_lookup (self->credentials_caches,
+ self->active_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+ best_verification_level = verify_identity_in_credentials_cache (self,
+ &best_preauth_identity_source,
+ credentials_cache,
+ &best_start_time,
+ &best_renewal_time,
+ &best_expiration_time,
+ error);
+ G_LOCK (identity_lock);
+ best_credentials_cache_name = g_strdup (self->active_credentials_cache_name);
+ G_UNLOCK (identity_lock);
+
+ if (best_verification_level == VERIFICATION_LEVEL_SIGNED_IN)
+ goto out;
+
+ if (best_verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
+ best_verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+ g_clear_pointer (&best_credentials_cache_name, g_free);
+
+ G_LOCK (identity_lock);
+ if (self->identifier != NULL)
+ {
+ krb5_cc_close (self->kerberos_context, credentials_cache);
+ g_hash_table_remove (self->credentials_caches, self->active_credentials_cache_name);
+ g_clear_pointer (&self->active_credentials_cache_name, g_free);
+ }
+ G_UNLOCK (identity_lock);
+ }
+ }
+
+- G_LOCK (identity_lock);
+- old_verification_level = self->cached_verification_level;
+- G_UNLOCK (identity_lock);
+-
+ G_LOCK (identity_lock);
+ g_hash_table_iter_init (&iter, self->credentials_caches);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &credentials_cache))
+ {
+ krb5_timestamp new_start_time = 0;
+ krb5_timestamp new_renewal_time = 0;
+ krb5_timestamp new_expiration_time = 0;
+ g_autofree char *new_preauth_identity_source = NULL;
+ VerificationLevel verification_level = VERIFICATION_LEVEL_UNVERIFIED;
+ gboolean has_better_credentials = FALSE;
+
+ if (g_strcmp0 (name, self->active_credentials_cache_name) == 0)
+ continue;
+
+ G_UNLOCK (identity_lock);
+
+ if (preauth_identity_source != NULL)
+ g_clear_pointer (preauth_identity_source, g_free);
+
+ verification_level = verify_identity_in_credentials_cache (self,
+ &new_preauth_identity_source,
+ credentials_cache,
+ &new_start_time,
+ &new_renewal_time,
+ &new_expiration_time,
+ error);
+
+ if (verification_level == VERIFICATION_LEVEL_UNVERIFIED ||
+ verification_level == VERIFICATION_LEVEL_ERROR)
+ {
+--
+2.39.3
+