/* securitytokenmonitor.c - monitor for security token insertion and * removal events * * Copyright (C) 2006 Ray Strode * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "securitytokenmonitor.h" #include #include #include #include #include #include #include #include #include #include #include "marshal.h" #ifndef GDM_SECURITY_TOKEN_MONITOR_ENABLE_TEST #include "misc.h" #else #define gdm_debug(format, args...) g_printerr(format "\n", ##args) #define gdm_warning(format, args...) g_printerr(format "\n", ##args) #endif #ifndef GDM_SECURITY_TOKEN_MONITOR_DRIVER #define GDM_SECURITY_TOKEN_MONITOR_DRIVER "/usr/lib/libcoolkeypk11.so" #endif #ifndef GDM_SECURITY_TOKEN_MONITOR_POLL_INTERVAL #define GDM_SECURITY_TOKEN_MONITOR_POLL_INTERVAL 100 /* ms */ #endif typedef enum _GdmSecurityTokenMonitorState GdmSecurityTokenMonitorState; struct _GdmSecurityToken { GdmSecurityTokenMonitor *monitor; CK_SLOT_ID slot_id; gint slot_series; }; enum _GdmSecurityTokenMonitorState { GDM_SECURITY_TOKEN_MONITOR_STATE_STOPPED = 0, GDM_SECURITY_TOKEN_MONITOR_STATE_STARTING, GDM_SECURITY_TOKEN_MONITOR_STATE_STARTED, }; struct _GdmSecurityTokenMonitorPrivate { GdmSecurityTokenMonitorState state; SECMODModule *module; GHashTable *security_tokens; guint32 nss_is_loaded : 1; guint poll_timeout_id; }; static void gdm_security_token_monitor_finalize (GObject *object); static void gdm_security_token_monitor_class_install_signals (GdmSecurityTokenMonitorClass *service_class); static GdmSecurityToken *gdm_security_token_new (GdmSecurityTokenMonitor *monitor, CK_SLOT_ID slot_id, gint slot_series); static void gdm_security_token_free (GdmSecurityToken *token); enum { SECURITY_TOKEN_INSERTED = 0, SECURITY_TOKEN_REMOVED, ERROR, NUMBER_OF_SIGNALS }; static guint gdm_security_token_monitor_signals[NUMBER_OF_SIGNALS]; G_DEFINE_TYPE (GdmSecurityTokenMonitor, gdm_security_token_monitor, G_TYPE_OBJECT); static void gdm_security_token_monitor_class_init (GdmSecurityTokenMonitorClass *monitor_class) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (monitor_class); gobject_class->finalize = gdm_security_token_monitor_finalize; gdm_security_token_monitor_class_install_signals (monitor_class); g_type_class_add_private (monitor_class, sizeof (GdmSecurityTokenMonitorPrivate)); } static void gdm_security_token_monitor_class_install_signals (GdmSecurityTokenMonitorClass *monitor_class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (monitor_class); gdm_security_token_monitor_signals[SECURITY_TOKEN_INSERTED] = g_signal_new ("security-token-inserted", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmSecurityTokenMonitorClass, security_token_inserted), NULL, NULL, gdm_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); monitor_class->security_token_inserted = NULL; gdm_security_token_monitor_signals[SECURITY_TOKEN_REMOVED] = g_signal_new ("security-token-removed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmSecurityTokenMonitorClass, security_token_removed), NULL, NULL, gdm_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); monitor_class->security_token_removed = NULL; gdm_security_token_monitor_signals[ERROR] = g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmSecurityTokenMonitorClass, error), NULL, NULL, gdm_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); monitor_class->error = NULL; } static void gdm_security_token_monitor_init (GdmSecurityTokenMonitor *monitor) { gdm_debug ("initializing security token monitor"); monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, GDM_TYPE_SECURITY_TOKEN_MONITOR, GdmSecurityTokenMonitorPrivate); monitor->priv->poll_timeout_id = 0; /* FIXME: maybe we should just byte the bullet and malloc the 4 byte * keys instead of asserting here in case things ever change. */ g_assert (sizeof (CK_SLOT_ID) <= sizeof (gint)); monitor->priv->security_tokens = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) gdm_security_token_free); } static void gdm_security_token_monitor_finalize (GObject *object) { GdmSecurityTokenMonitor *monitor; GObjectClass *gobject_class; monitor = GDM_SECURITY_TOKEN_MONITOR (object); gobject_class = G_OBJECT_CLASS (gdm_security_token_monitor_parent_class); g_hash_table_destroy (monitor->priv->security_tokens); gdm_security_token_monitor_stop (monitor); gobject_class->finalize (object); } GQuark gdm_security_token_monitor_error_quark (void) { static GQuark error_quark = 0; if (error_quark == 0) error_quark = g_quark_from_static_string ("gdm-security-token-monitor-error-quark"); return error_quark; } GdmSecurityTokenMonitor * gdm_security_token_monitor_new (void) { GdmSecurityTokenMonitor *instance; instance = GDM_SECURITY_TOKEN_MONITOR (g_object_new (GDM_TYPE_SECURITY_TOKEN_MONITOR, NULL)); return instance; } static gboolean gdm_security_token_monitor_check_for_and_process_events (GdmSecurityTokenMonitor *monitor) { PK11SlotInfo *slot; CK_SLOT_ID slot_id; gint slot_series; GdmSecurityToken *token; slot = SECMOD_WaitForAnyTokenEvent (monitor->priv->module, CKF_DONT_BLOCK, PR_INTERVAL_NO_WAIT); if (slot == NULL) { GError *error; int error_code; error_code = PORT_GetError (); if ((error_code == 0) || (error_code == SEC_ERROR_NO_EVENT)) return TRUE; /* FIXME: is there a function to convert from a PORT error * code to a translated string? */ error = g_error_new (GDM_SECURITY_TOKEN_MONITOR_ERROR, GDM_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, _("encountered unexpected error while waiting for " "security token events")); g_signal_emit (monitor, gdm_security_token_monitor_signals[ERROR], 0, error); g_error_free (error); return FALSE; } /* the slot id and series together uniquely identify a token. * You can never have two tokens with the same slot id at the * same time, however (I think), so we can key off of it. */ slot_id = PK11_GetSlotID (slot); slot_series = PK11_GetSlotSeries (slot); /* First check to see if there is a token that we're currently * tracking in the slot. */ token = g_hash_table_lookup (monitor->priv->security_tokens, GINT_TO_POINTER (slot_id)); if (PK11_IsPresent (slot)) { /* Now, check to see if their is a new token in the slot. * If there was a different token in the slot now than * there was before, then we need to emit a removed signal * for the old token. * * FIXME: So, I *think* the reason we need to do this is * for the case where a token is removed and another * inserted faster than the internal poll granualarity of * NSS. In that case we won't get a separate removed event * so we need to handle it ourselves. But what happens if * a token is removed, another inserted, then removed, and * another inserted really fast? Shouldn't we emit * several removed/inserted events? If so, can we depend * on the difference of the series numbers to determine * how many events to emit? It's all hard to trigger * corner cases that probably don't matter anyhow, though. */ if ((token != NULL) && (token->slot_series != slot_series)) { g_signal_emit (monitor, gdm_security_token_monitor_signals[SECURITY_TOKEN_REMOVED], 0, token); } token = gdm_security_token_new (monitor, slot_id, slot_series); g_hash_table_replace (monitor->priv->security_tokens, GINT_TO_POINTER (token->slot_id), token); g_signal_emit (monitor, gdm_security_token_monitor_signals[SECURITY_TOKEN_INSERTED], 0, token); } else { /* if we aren't tracking the token, just discard the event. * We don't want unpaired remove events. Note on startup * NSS will generate an "insertion" event if a token is * already inserted in the slot. */ if ((token != NULL)) { /* FIXME: i'm not sure about this code. Maybe we * shouldn't do this at all, or maybe we should do it * n times (where n = slot_series - token->slot_series + 1) * (see the comment in the if part of this if/else * clause for more information) * * Right now, i'm just doing it once. */ if (((slot_series - token->slot_series) > 1)) { g_signal_emit (monitor, gdm_security_token_monitor_signals[SECURITY_TOKEN_REMOVED], 0, token); g_hash_table_remove (monitor->priv->security_tokens, GINT_TO_POINTER (token->slot_id)); token = gdm_security_token_new (monitor, slot_id, slot_series); g_hash_table_replace (monitor->priv->security_tokens, GINT_TO_POINTER (token->slot_id), token); g_signal_emit (monitor, gdm_security_token_monitor_signals[SECURITY_TOKEN_INSERTED], 0, token); } g_signal_emit (monitor, gdm_security_token_monitor_signals[SECURITY_TOKEN_REMOVED], 0, token); g_hash_table_remove (monitor->priv->security_tokens, GINT_TO_POINTER (token->slot_id)); token = NULL; } } PK11_FreeSlot (slot); return TRUE; } static void gdm_security_token_monitor_polling_stopped_handler (GdmSecurityTokenMonitor *monitor) { monitor->priv->poll_timeout_id = 0; gdm_security_token_monitor_stop (monitor); } gboolean gdm_security_token_monitor_start (GdmSecurityTokenMonitor *monitor, GError **error) { SECStatus status; gchar *module_spec; gdm_debug ("starting security token monitor"); if (monitor->priv->state == GDM_SECURITY_TOKEN_MONITOR_STATE_STARTED) { gdm_debug ("security token monitor already started"); return TRUE; } g_assert (monitor->priv->state != GDM_SECURITY_TOKEN_MONITOR_STATE_STARTING); monitor->priv->state = GDM_SECURITY_TOKEN_MONITOR_STATE_STARTING; status = NSS_NoDB_Init (NULL); if (status != SECSuccess) { gsize error_message_size; gchar *error_message; error_message_size = PR_GetErrorTextLength (); if (error_message_size == 0) { gdm_debug ("NSS security system could not be initialized"); g_set_error (error, GDM_SECURITY_TOKEN_MONITOR_ERROR, GDM_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, _("NSS security system could not be initialized")); goto out; } error_message = g_slice_alloc0 (error_message_size); PR_GetErrorText (error_message); g_set_error (error, GDM_SECURITY_TOKEN_MONITOR_ERROR, GDM_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, "%s", error_message); gdm_debug ("NSS security system could not be initialized - %s", error_message); g_slice_free1 (error_message_size, error_message); goto out; } monitor->priv->nss_is_loaded = TRUE; module_spec = g_strdup_printf ("library=\"%s\" name=\"SmartCard\"", GDM_SECURITY_TOKEN_MONITOR_DRIVER); /* FIXME: this API is apparently deprecated, find out what is replacing it */ gdm_debug ("loading security token driver using spec '%s'", module_spec); monitor->priv->module = SECMOD_LoadUserModule (module_spec, NULL, 0); g_free (module_spec); if (monitor->priv->module == NULL) { gsize error_message_size; gchar *error_message; error_message_size = PR_GetErrorTextLength (); if (error_message_size == 0) { gdm_debug ("security token driver '%s' could not be loaded", GDM_SECURITY_TOKEN_MONITOR_DRIVER); g_set_error (error, GDM_SECURITY_TOKEN_MONITOR_ERROR, GDM_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, _("security token driver '%s' could not be loaded"), GDM_SECURITY_TOKEN_MONITOR_DRIVER); goto out; } error_message = g_slice_alloc0 (error_message_size); PR_GetErrorText (error_message); g_set_error (error, GDM_SECURITY_TOKEN_MONITOR_ERROR, GDM_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, "%s", error_message); gdm_debug ("security token driver '%s' could not be loaded - %s", GDM_SECURITY_TOKEN_MONITOR_DRIVER, error_message); g_slice_free1 (error_message_size, error_message); goto out; } /* FIXME: so it sort of sucks that we have to poll here. NSS * does offer a non-blocking API (that we use), but there is * no way to wake up the event loop when new events arrive, so * we just have to poll periodically. An alternative would be * to fork (or bring in threads, ick) and use the blocking api * plus a message pipe, but that's significantly more * complicated to implement. Maybe a better solution is to * move all this off to Yet Another Daemon Process and have it * emit dbus signals that we watch for. */ monitor->priv->poll_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, GDM_SECURITY_TOKEN_MONITOR_POLL_INTERVAL, (GSourceFunc) gdm_security_token_monitor_check_for_and_process_events, monitor, (GDestroyNotify) gdm_security_token_monitor_polling_stopped_handler); monitor->priv->state = GDM_SECURITY_TOKEN_MONITOR_STATE_STARTED; out: /* don't leave it in a half started started */ if (monitor->priv->state != GDM_SECURITY_TOKEN_MONITOR_STATE_STARTED) { gdm_debug ("security token monitor could not be completely started"); gdm_security_token_monitor_stop (monitor); } else gdm_debug ("security token monitor started"); return monitor->priv->state == GDM_SECURITY_TOKEN_MONITOR_STATE_STARTED; } void gdm_security_token_monitor_stop (GdmSecurityTokenMonitor *monitor) { if (monitor->priv->state == GDM_SECURITY_TOKEN_MONITOR_STATE_STOPPED) return; gdm_debug ("stopping security token monitor"); monitor->priv->state = GDM_SECURITY_TOKEN_MONITOR_STATE_STOPPED; if (monitor->priv->poll_timeout_id != 0) { g_source_remove (monitor->priv->poll_timeout_id); monitor->priv->poll_timeout_id = 0; } if (monitor->priv->module != NULL) { SECMOD_DestroyModule (monitor->priv->module); monitor->priv->module = NULL; } if (monitor->priv->nss_is_loaded) { NSS_Shutdown (); monitor->priv->nss_is_loaded = FALSE; } gdm_debug ("security token monitor stopped"); } static GdmSecurityToken * gdm_security_token_new (GdmSecurityTokenMonitor *monitor, CK_SLOT_ID slot_id, gint slot_series) { GdmSecurityToken *token; token = g_slice_new0 (GdmSecurityToken); token->monitor = monitor; token->slot_id = slot_id; token->slot_series = slot_series; return token; } static void gdm_security_token_free (GdmSecurityToken *token) { if (token == NULL) return; g_slice_free (GdmSecurityToken, token); } #ifdef GDM_SECURITY_TOKEN_MONITOR_ENABLE_TEST #include static GMainLoop *event_loop; static gboolean should_exit_on_next_remove = FALSE; static gboolean on_timeout (GdmSecurityTokenMonitor *monitor) { GError *error; g_print ("Re-enabling monitor.\n"); if (!gdm_security_token_monitor_start (monitor, &error)) { g_warning ("could not start security token monitor - %s", error->message); g_error_free (error); return 1; } g_print ("Please re-insert security token\n"); should_exit_on_next_remove = TRUE; return FALSE; } static void on_device_inserted (GdmSecurityTokenMonitor *monitor, GdmSecurityToken *token) { g_print ("security token inserted!\n"); g_print ("Please remove it.\n"); } static void on_device_removed (GdmSecurityTokenMonitor *monitor, GdmSecurityToken *token) { g_print ("security token removed!\n"); if (should_exit_on_next_remove) g_main_loop_quit (event_loop); else { g_print ("disabling monitor for 2 seconds\n"); gdm_security_token_monitor_stop (monitor); g_timeout_add (2000, (GSourceFunc) on_timeout, monitor); } } int main (int argc, char *argv[]) { GdmSecurityTokenMonitor *monitor; GError *error; g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); g_type_init (); g_message ("creating instance of 'security token monitor' object..."); monitor = gdm_security_token_monitor_new (); g_message ("'security token monitor' object created successfully"); g_signal_connect (monitor, "security-token-inserted", G_CALLBACK (on_device_inserted), NULL); g_signal_connect (monitor, "security-token-removed", G_CALLBACK (on_device_removed), NULL); g_message ("starting listener..."); error = NULL; if (!gdm_security_token_monitor_start (monitor, &error)) { g_warning ("could not start security token monitor - %s", error->message); g_error_free (error); return 1; } event_loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (event_loop); g_main_loop_unref (event_loop); event_loop = NULL; g_message ("destroying previously created 'security token monitor' object..."); g_object_unref (monitor); monitor = NULL; g_message ("'security token monitor' object destroyed successfully"); return 0; } #endif