From 3aa4de5e6f2b698abf063ddbea5008f5b732aa18 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Fri, 9 Feb 2018 16:40:53 -0500
Subject: [PATCH 2/2] smartcard: handle a smartcard getting removed very
 shortly after login

Right now we depend on the smartcard used at login time to be inserted,
at least long enough to read some basic stats about it.  This
assumption, of course doesn't necessarly need to hold true.  A user
could remove the smartcard immediately after login and we would
misreport that the card wasn't used for login at all.

This commit addresses that edge case by creating a login_token
smartcard alias object that's around even if the login token isn't.
Once the login token does show up it gets synchronized with it, so
both object paths refer to the same underlying token.
---
 plugins/smartcard/gsd-smartcard-service.c | 82 +++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/plugins/smartcard/gsd-smartcard-service.c b/plugins/smartcard/gsd-smartcard-service.c
index 0710334b..6969ff34 100644
--- a/plugins/smartcard/gsd-smartcard-service.c
+++ b/plugins/smartcard/gsd-smartcard-service.c
@@ -11,60 +11,61 @@
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  *
  * Authors: Ray Strode
  */
 
 #include "config.h"
 
 #include "gsd-smartcard-service.h"
 #include "org.gnome.SettingsDaemon.Smartcard.h"
 #include "gsd-smartcard-manager.h"
 #include "gsd-smartcard-enum-types.h"
 #include "gsd-smartcard-utils.h"
 
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <gio/gio.h>
 
 struct _GsdSmartcardServicePrivate
 {
         GDBusConnection            *bus_connection;
         GDBusObjectManagerServer   *object_manager_server;
         GsdSmartcardManager        *smartcard_manager;
         GCancellable               *cancellable;
         GHashTable                 *tokens;
 
+        gboolean                    login_token_bound;
         guint name_id;
 };
 
 #define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
 #define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
 #define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
 
 #define GSD_SMARTCARD_DBUS_NAME GSD_DBUS_NAME ".Smartcard"
 #define GSD_SMARTCARD_DBUS_PATH GSD_DBUS_PATH "/Smartcard"
 #define GSD_SMARTCARD_MANAGER_DBUS_PATH GSD_SMARTCARD_DBUS_PATH "/Manager"
 #define GSD_SMARTCARD_MANAGER_DRIVERS_DBUS_PATH GSD_SMARTCARD_MANAGER_DBUS_PATH "/Drivers"
 #define GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH  GSD_SMARTCARD_MANAGER_DBUS_PATH "/Tokens"
 
 enum {
         PROP_0,
         PROP_MANAGER,
         PROP_BUS_CONNECTION
 };
 
 static void gsd_smartcard_service_set_property (GObject *object,
                                                 guint property_id,
                                                 const GValue *value,
                                                 GParamSpec *param_spec);
 static void gsd_smartcard_service_get_property (GObject *object,
                                                 guint property_id,
                                                 GValue *value,
                                                 GParamSpec *param_spec);
 static void async_initable_interface_init (GAsyncInitableIface *interface);
 static void smartcard_service_manager_interface_init (GsdSmartcardServiceManagerIface *interface);
 
@@ -83,91 +84,139 @@ set_bus_connection (GsdSmartcardService  *self,
                     GDBusConnection      *connection)
 {
         GsdSmartcardServicePrivate *priv = self->priv;
 
         if (priv->bus_connection != connection) {
                 g_clear_object (&priv->bus_connection);
                 priv->bus_connection = g_object_ref (connection);
                 g_object_notify (G_OBJECT (self), "bus-connection");
         }
 }
 
 static void
 register_object_manager (GsdSmartcardService *self)
 {
         GsdSmartcardServiceObjectSkeleton *object;
 
         self->priv->object_manager_server = g_dbus_object_manager_server_new (GSD_SMARTCARD_DBUS_PATH);
 
         object = gsd_smartcard_service_object_skeleton_new (GSD_SMARTCARD_MANAGER_DBUS_PATH);
         gsd_smartcard_service_object_skeleton_set_manager (object,
                                                            GSD_SMARTCARD_SERVICE_MANAGER (self));
 
         g_dbus_object_manager_server_export (self->priv->object_manager_server,
                                              G_DBUS_OBJECT_SKELETON (object));
         g_object_unref (object);
 
         g_dbus_object_manager_server_set_connection (self->priv->object_manager_server,
                                                      self->priv->bus_connection);
 }
 
+static const char *
+get_login_token_object_path (GsdSmartcardService *self)
+{
+        return GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH "/login_token";
+}
+
+static void
+register_login_token_alias (GsdSmartcardService *self)
+{
+        GsdSmartcardServicePrivate *priv;
+        GDBusObjectSkeleton *object;
+        GDBusInterfaceSkeleton *interface;
+        const char *object_path;
+        const char *token_name;
+
+        token_name = g_getenv ("PKCS11_LOGIN_TOKEN_NAME");
+
+        if (token_name == NULL)
+                return;
+
+        priv = self->priv;
+
+        object_path = get_login_token_object_path (self);
+        object = G_DBUS_OBJECT_SKELETON (gsd_smartcard_service_object_skeleton_new (object_path));
+        interface = G_DBUS_INTERFACE_SKELETON (gsd_smartcard_service_token_skeleton_new ());
+
+        g_dbus_object_skeleton_add_interface (object, interface);
+        g_object_unref (interface);
+
+        g_object_set (G_OBJECT (interface),
+                      "name", token_name,
+                      "used-to-login", TRUE,
+                      "is-inserted", FALSE,
+                      NULL);
+
+        g_dbus_object_manager_server_export (self->priv->object_manager_server,
+                                             object);
+
+        G_LOCK (gsd_smartcard_tokens);
+        g_hash_table_insert (priv->tokens, g_strdup (object_path), interface);
+        G_UNLOCK (gsd_smartcard_tokens);
+}
+
 static void
 on_bus_gotten (GObject      *source_object,
                GAsyncResult *result,
                GTask        *task)
 {
         GsdSmartcardService *self;
         GsdSmartcardServicePrivate *priv;
         GDBusConnection *connection;
         GError *error = NULL;
 
         connection = g_bus_get_finish (result, &error);
         if (connection == NULL) {
                 g_task_return_error (task, error);
                 goto out;
         }
 
         g_debug ("taking name %s on session bus", GSD_SMARTCARD_DBUS_NAME);
 
         self = g_task_get_source_object (task);
         priv = self->priv;
 
         set_bus_connection (self, connection);
 
         register_object_manager (self);
         priv->name_id = g_bus_own_name_on_connection (connection,
                                                       GSD_SMARTCARD_DBUS_NAME,
                                                       G_BUS_NAME_OWNER_FLAGS_NONE,
                                                       NULL,
                                                       NULL,
                                                       NULL,
                                                       NULL);
+
+        /* In case the login token is removed at start up, register an
+         * an alias interface that's always around
+         */
+        register_login_token_alias (self);
         g_task_return_boolean (task, TRUE);
 
 out:
         g_object_unref (task);
         return;
 }
 
 static gboolean
 gsd_smartcard_service_initable_init_finish (GAsyncInitable  *initable,
                                             GAsyncResult    *result,
                                             GError         **error)
 {
         GTask *task;
 
         task = G_TASK (result);
 
         return g_task_propagate_boolean (task, error);
 }
 
 static void
 gsd_smartcard_service_initable_init_async (GAsyncInitable      *initable,
                                            int                  io_priority,
                                            GCancellable        *cancellable,
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
 {
         GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (initable);
         GTask *task;
 
         task = g_task_new (G_OBJECT (self), cancellable, callback, user_data);
@@ -191,60 +240,75 @@ get_object_path_for_token (GsdSmartcardService *self,
         char *escaped_library_path;
         SECMODModule *driver;
         CK_SLOT_ID slot_id;
 
         driver = PK11_GetModule (card_slot);
         slot_id = PK11_GetSlotID (card_slot);
 
         escaped_library_path = gsd_smartcard_utils_escape_object_path (driver->dllName);
 
         object_path = g_strdup_printf ("%s/token_from_%s_slot_%lu",
                                        GSD_SMARTCARD_MANAGER_TOKENS_DBUS_PATH,
                                        escaped_library_path,
                                        (gulong) slot_id);
         g_free (escaped_library_path);
 
         return object_path;
 }
 
 static gboolean
 gsd_smartcard_service_handle_get_login_token (GsdSmartcardServiceManager *manager,
                                               GDBusMethodInvocation      *invocation)
 {
         GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (manager);
         GsdSmartcardServicePrivate *priv = self->priv;
         PK11SlotInfo *card_slot;
         char *object_path;
 
         card_slot = gsd_smartcard_manager_get_login_token (priv->smartcard_manager);
 
         if (card_slot == NULL) {
+                const char *login_token_object_path;
+
+                /* If we know there's a login token but it was removed before the
+                 * smartcard manager could examine it, just return the generic login
+                 * token object path
+                 */
+                login_token_object_path = get_login_token_object_path (self);
+
+                if (g_hash_table_contains (priv->tokens, login_token_object_path)) {
+                        gsd_smartcard_service_manager_complete_get_login_token (manager,
+                                                                                invocation,
+                                                                                login_token_object_path);
+                        return TRUE;
+                }
+
                 g_dbus_method_invocation_return_error (invocation,
                                                        GSD_SMARTCARD_MANAGER_ERROR,
                                                        GSD_SMARTCARD_MANAGER_ERROR_FINDING_SMARTCARD,
                                                        _("User was not logged in with smartcard."));
 
                 return TRUE;
         }
 
         object_path = get_object_path_for_token (self, card_slot);
         gsd_smartcard_service_manager_complete_get_login_token (manager,
                                                                 invocation,
                                                                 object_path);
         g_free (object_path);
 
         return TRUE;
 }
 
 static gboolean
 gsd_smartcard_service_handle_get_inserted_tokens (GsdSmartcardServiceManager *manager,
                                                   GDBusMethodInvocation      *invocation)
 {
         GsdSmartcardService *self = GSD_SMARTCARD_SERVICE (manager);
         GsdSmartcardServicePrivate *priv = self->priv;
         GList *inserted_tokens, *node;
         GPtrArray *object_paths;
 
         inserted_tokens = gsd_smartcard_manager_get_inserted_tokens (priv->smartcard_manager,
                                                                      NULL);
 
         object_paths = g_ptr_array_new ();
@@ -498,60 +562,78 @@ synchronize_token_now (GsdSmartcardService *self,
                 is_login_card = TRUE;
         else
                 is_login_card = FALSE;
 
         g_debug ("===============================");
         g_debug (" Token '%s'", token_name);
         g_debug (" Inserted: %s", is_present? "yes" : "no");
         g_debug (" Previously used to login: %s", is_login_card? "yes" : "no");
         g_debug ("===============================\n");
 
         if (!is_present && is_login_card) {
                 gboolean was_present;
 
                 g_object_get (G_OBJECT (interface),
                               "is-inserted", &was_present,
                               NULL);
 
                 if (was_present)
                         gsd_smartcard_manager_do_remove_action (priv->smartcard_manager);
         }
 
         g_object_set (G_OBJECT (interface),
                       "used-to-login", is_login_card,
                       "is-inserted", is_present,
                       NULL);
         g_object_get (G_OBJECT (interface),
                       "used-to-login", &is_login_card,
                       "is-inserted", &is_present,
                       NULL);
 
+        if (is_login_card && !priv->login_token_bound) {
+                const char *login_token_path;
+                GDBusInterfaceSkeleton *login_token_interface;
+
+                login_token_path = get_login_token_object_path (self);
+                login_token_interface = g_hash_table_lookup (priv->tokens, login_token_path);
+
+                if (login_token_interface != NULL) {
+                        g_object_bind_property (interface, "driver",
+                                                login_token_interface, "driver",
+                                                G_BINDING_SYNC_CREATE);
+                        g_object_bind_property (interface, "is-inserted",
+                                                login_token_interface, "is-inserted",
+                                                G_BINDING_SYNC_CREATE);
+                        priv->login_token_bound = TRUE;
+                }
+        }
+
 out:
         G_UNLOCK (gsd_smartcard_tokens);
 }
 
 typedef struct
 {
         PK11SlotInfo *card_slot;
         char         *object_path;
         GSource      *main_thread_source;
 } RegisterNewTokenOperation;
 
 static void
 destroy_register_new_token_operation (RegisterNewTokenOperation *operation)
 {
         g_clear_pointer (&operation->main_thread_source,
                          (GDestroyNotify) g_source_destroy);
         PK11_FreeSlot (operation->card_slot);
         g_free (operation->object_path);
         g_free (operation);
 }
 
 static gboolean
 on_main_thread_to_register_new_token (GTask *task)
 {
         GsdSmartcardService *self;
         GsdSmartcardServicePrivate *priv;
         GDBusObjectSkeleton *object;
         GDBusInterfaceSkeleton *interface;
         RegisterNewTokenOperation *operation;
         SECMODModule *driver;
-- 
2.17.0

