--- gdm-2.19.1/configure.ac.security-tokens 2007-05-21 16:04:51.000000000 -0400 +++ gdm-2.19.1/configure.ac 2007-05-21 16:04:51.000000000 -0400 @@ -20,6 +20,7 @@ LIBRSVG_REQUIRED=1.1.1 LIBXML_REQUIRED=2.4.12 LIBART_REQUIRED=2.3.11 SCROLLKEEPER_REQUIRED=0.1.4 +NSS_REQUIRED=3.11.1 dnl dnl Let the user configure where to look for the configuration files. @@ -176,7 +177,7 @@ PKG_CHECK_MODULES(COMMON, gtk+-2.0 >= $G AC_SUBST(COMMON_CFLAGS) AC_SUBST(COMMON_LIBS) -PKG_CHECK_MODULES(DAEMON, gtk+-2.0 >= $GTK_REQUIRED) +PKG_CHECK_MODULES(DAEMON, gtk+-2.0 >= $GTK_REQUIRED nss >= $NSS_REQUIRED) AC_SUBST(DAEMON_CFLAGS) AC_SUBST(DAEMON_LIBS) --- /dev/null 2007-05-21 09:34:56.803421964 -0400 +++ gdm-2.19.1/config/securitytokens.conf.in 2007-05-21 16:04:51.000000000 -0400 @@ -0,0 +1,3 @@ +[SecurityTokens] +Enable=true +#Driver=@libdir@/pkcs11/libcoolkeypk11.so --- gdm-2.19.1/config/Makefile.am.security-tokens 2007-05-13 22:08:25.000000000 -0400 +++ gdm-2.19.1/config/Makefile.am 2007-05-21 16:04:51.000000000 -0400 @@ -34,9 +34,11 @@ EXTRA_DIST = \ XKeepsCrashing \ gettextfoo.h \ gdmprefetchlist.in \ + securitytokens.conf.in \ extract-shell.sh -CLEANFILES = Xsession gdm.conf gdm.conf-custom default.desktop gnome.desktop CDE.desktop ssh.desktop Init PreSession PostSession gdmprefetchlist +CLEANFILES = Xsession gdm.conf gdm.conf-custom default.desktop gnome.desktop CDE.desktop ssh.desktop Init PreSession PostSession gdmprefetchlist securitytokens.conf + Xsession: $(srcdir)/Xsession.in sed -e 's,[@]XSESSION_SHELL[@],$(XSESSION_SHELL),g' \ @@ -75,6 +77,31 @@ gdm.conf-custom: $(srcdir)/gdm.conf-cust sed -e 's,[@]GDM_DEFAULTS_CONF[@],$(GDM_DEFAULTS_CONF),g' \ <$(srcdir)/gdm.conf-custom.in >gdm.conf-custom +securitytokens.conf: $(srcdir)/securitytokens.conf.in + sed -e 's,[@]GDMPREFETCHCMD[@],$(GDMPREFETCHCMD),g' \ + -e 's,[@]GDM_USER_PATH[@],$(GDM_USER_PATH),g' \ + -e 's,[@]HALT_COMMAND[@],$(HALT_COMMAND),g' \ + -e 's,[@]REBOOT_COMMAND[@],$(REBOOT_COMMAND),g' \ + -e 's,[@]SOUND_PROGRAM[@],$(SOUND_PROGRAM),g' \ + -e 's,[@]SUSPEND_COMMAND[@],$(SUSPEND_COMMAND),g' \ + -e 's,[@]XEVIE_OPTION[@],$(XEVIE_OPTION),g' \ + -e 's,[@]X_CONFIG_OPTIONS[@],$(X_CONFIG_OPTIONS),g' \ + -e 's,[@]X_SERVER[@],$(X_SERVER),g' \ + -e 's,[@]X_XNEST_CONFIG_OPTIONS[@],$(X_XNEST_CONFIG_OPTIONS),g' \ + -e 's,[@]X_XNEST_PATH[@],$(X_XNEST_PATH),g' \ + -e 's,[@]authdir[@],$(authdir),g' \ + -e 's,[@]datadir[@],$(datadir),g' \ + -e 's,[@]dmconfdir[@],$(dmconfdir),g' \ + -e 's,[@]gdmconfdir[@],$(gdmconfdir),g' \ + -e 's,[@]libdir[@],$(libdir),g' \ + -e 's,[@]libexecdir[@],$(libexecdir),g' \ + -e 's,[@]localedir[@],$(libexecdir),g' \ + -e 's,[@]logdir[@],$(logdir),g' \ + -e 's,[@]pixmapdir[@],$(pixmapdir),g' \ + -e 's,[@]sbindir[@],$(sbindir),g' \ + <$(srcdir)/securitytokens.conf.in >securitytokens.conf + + gettextfoo.h: XKeepsCrashing Xsession.in cat $^ | $(srcdir)/extract-shell.sh > gettextfoo.h @@ -103,7 +130,7 @@ uninstall-hook: $(DESTDIR)$(predir)/Default \ $(DESTDIR)$(postdir)/Default -install-data-hook: gdm.conf gdm.conf-custom Xsession Init PostSession PreSession $(DESKTOP_FILES) $(GDMPREFETCHLIST) +install-data-hook: gdm.conf gdm.conf-custom Xsession Init PostSession PreSession $(DESKTOP_FILES) $(GDMPREFETCHLIST) securitytokens.conf if test '!' -d $(DESTDIR)$(confdir); then \ $(mkinstalldirs) $(DESTDIR)$(confdir); \ chmod 755 $(DESTDIR)$(confdir); \ @@ -136,6 +163,7 @@ install-data-hook: gdm.conf gdm.conf-cus chmod 644 $(DESTDIR)$(GDM_CUSTOM_CONF); \ fi $(INSTALL_DATA) gdm.conf `dirname $(DESTDIR)$(GDM_DEFAULTS_CONF)`/factory-`basename $(DESTDIR)$(GDM_DEFAULTS_CONF)` + $(INSTALL_DATA) securitytokens.conf $(DESTDIR)$(confdir)/securitytokens.conf $(INSTALL_SCRIPT) $(srcdir)/XKeepsCrashing $(DESTDIR)$(confdir)/XKeepsCrashing $(INSTALL_SCRIPT) Xsession $(DESTDIR)$(confdir)/Xsession --- gdm-2.19.1/config/gdm.conf.in.security-tokens 2007-05-21 16:04:51.000000000 -0400 +++ gdm-2.19.1/config/gdm.conf.in 2007-05-21 16:04:51.000000000 -0400 @@ -239,6 +239,10 @@ AlwaysLoginCurrentSession=true # kills it. 10 seconds should be long enough for X, but Xgl may need 20 or 25. GdmXserverTimeout=10 +# Whether or not to listen for smart card insertion/removal events +SecurityTokensEnable=true +SecurityTokensDriver= + [security] # Allow root to login. It makes sense to turn this off for kiosk use, when # you want to minimize the possibility of break in. --- gdm-2.19.1/daemon/gdm.c.security-tokens 2007-05-21 16:04:51.000000000 -0400 +++ gdm-2.19.1/daemon/gdm.c 2007-05-21 16:07:29.000000000 -0400 @@ -42,6 +42,7 @@ #include #include #include +#include #ifdef HAVE_CHKAUTHATTR #include @@ -71,6 +72,8 @@ #include "cookie.h" #include "filecheck.h" #include "errorgui.h" +#include "securitytokenmonitor.h" +#include "securitytoken.h" #include "gdm-socket-protocol.h" #include "gdm-daemon-config.h" @@ -93,6 +96,10 @@ static void gdm_handle_message (GdmConne static void gdm_handle_user_message (GdmConnection *conn, const gchar *msg, gpointer data); + +static void gdm_reset_local_displays (void); +static void gdm_watch_for_security_tokens (void); + static void gdm_daemonify (void); static void gdm_safe_restart (void); static void gdm_try_logout_action (GdmDisplay *disp); @@ -1787,6 +1794,8 @@ main (int argc, char *argv[]) gdm_xdmcp_run (); } + gdm_watch_for_security_tokens (); + /* We always exit via exit (), and sadly we need to g_main_quit () * at times not knowing if it's this main or a recursive one we're * quitting. @@ -4342,3 +4351,91 @@ gdm_handle_user_message (GdmConnection * gdm_connection_close (conn); } } + +static void +gdm_reset_local_displays (void) +{ + GSList *li; + GSList *displays; + + displays = gdm_daemon_config_get_display_list (); + + for (li = displays; li != NULL; li = li->next) { + GdmDisplay *d = li->data; + + if (d->attached) + send_slave_command (d, GDM_NOTIFY_RESET); + } +} + +#ifndef GDM_SECURITY_TOKENS_CONF +#define GDM_SECURITY_TOKENS_CONF GDMCONFDIR "/securitytokens.conf" +#endif + +#ifndef GDM_SECURITY_TOKENS_GROUP +#define GDM_SECURITY_TOKENS_GROUP "SecurityTokens" +#endif + +#ifndef GDM_SECURITY_TOKENS_KEY_ENABLED +#define GDM_SECURITY_TOKENS_KEY_ENABLED "Enabled" +#endif + +#ifndef GDM_SECURITY_TOKENS_KEY_DRIVER +#define GDM_SECURITY_TOKENS_KEY_DRIVER "Driver" +#endif + +static void +gdm_watch_for_security_tokens (void) +{ + GError *error; + ScSecurityTokenMonitor *monitor; + gchar *driver; + GKeyFile *cfg; + + cfg = g_key_file_new (); + if (!g_key_file_load_from_file (cfg, GDM_SECURITY_TOKENS_CONF, 0, NULL)) { + goto out; + } + + if (!g_key_file_get_boolean (cfg, GDM_SECURITY_TOKENS_GROUP, GDM_SECURITY_TOKENS_KEY_ENABLED, NULL)) { + + gdm_debug ("security token support is not enabled"); + goto out; + } + + gdm_debug ("watching for security token insertion and removal events"); + + driver = g_key_file_get_string (cfg, GDM_SECURITY_TOKENS_GROUP, GDM_SECURITY_TOKENS_KEY_DRIVER, NULL); + gdm_debug ("security tokens driver is set to '%s'", + ve_string_empty (driver)? "" : driver); + monitor = sc_security_token_monitor_new (driver); + g_free (driver); + + g_signal_connect (monitor, + "security-token-inserted", + G_CALLBACK (gdm_reset_local_displays), + NULL); + + g_signal_connect (monitor, + "security-token-removed", + G_CALLBACK (gdm_reset_local_displays), + NULL); + + error = NULL; + if (!sc_security_token_monitor_start (monitor, &error)) { + g_object_unref (monitor); + monitor = NULL; + + if (error != NULL) { + syslog (LOG_ERR, "%s", error->message); + g_error_free (error); + } else { + syslog (LOG_ERR, "could not start security token monitor"); + + } + goto out; + } +out: + g_key_file_free (cfg); +} + --- /dev/null 2007-05-21 09:34:56.803421964 -0400 +++ gdm-2.19.1/daemon/securitytokenmonitor.h 2007-05-21 16:04:51.000000000 -0400 @@ -0,0 +1,84 @@ +/* securitytokenmonitor.h - 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. + */ +#ifndef SC_SECURITY_TOKEN_MONITOR_H +#define SC_SECURITY_TOKEN_MONITOR_H + +#define SC_SECURITY_TOKEN_ENABLE_INTERNAL_API +#include "securitytoken.h" + +#include +#include + +G_BEGIN_DECLS +#define SC_TYPE_SECURITY_TOKEN_MONITOR (sc_security_token_monitor_get_type ()) +#define SC_SECURITY_TOKEN_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SC_TYPE_SECURITY_TOKEN_MONITOR, ScSecurityTokenMonitor)) +#define SC_SECURITY_TOKEN_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SC_TYPE_SECURITY_TOKEN_MONITOR, ScSecurityTokenMonitorClass)) +#define SC_IS_SECURITY_TOKEN_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_SECURITY_TOKEN_MONITOR)) +#define SC_IS_SECURITY_TOKEN_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SC_TYPE_SECURITY_TOKEN_MONITOR)) +#define SC_SECURITY_TOKEN_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SC_TYPE_SECURITY_TOKEN_MONITOR, ScSecurityTokenMonitorClass)) +#define SC_SECURITY_TOKEN_MONITOR_ERROR (sc_security_token_monitor_error_quark ()) +typedef struct _ScSecurityTokenMonitor ScSecurityTokenMonitor; +typedef struct _ScSecurityTokenMonitorClass ScSecurityTokenMonitorClass; +typedef struct _ScSecurityTokenMonitorPrivate ScSecurityTokenMonitorPrivate; +typedef enum _ScSecurityTokenMonitorError ScSecurityTokenMonitorError; + +struct _ScSecurityTokenMonitor { + GObject parent; + + /*< private > */ + ScSecurityTokenMonitorPrivate *priv; +}; + +struct _ScSecurityTokenMonitorClass { + GObjectClass parent_class; + + /* Signals */ + void (*security_token_inserted) (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token); + void (*security_token_removed) (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token); + void (*error) (ScSecurityTokenMonitor *monitor, + GError *error); +}; + +enum _ScSecurityTokenMonitorError { + SC_SECURITY_TOKEN_MONITOR_ERROR_GENERIC = 0, + SC_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, + SC_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, + SC_SECURITY_TOKEN_MONITOR_ERROR_WATCHING_FOR_EVENTS, + SC_SECURITY_TOKEN_MONITOR_ERROR_REPORTING_EVENTS +}; + +GType sc_security_token_monitor_get_type (void) G_GNUC_CONST; +GQuark sc_security_token_monitor_error_quark (void) G_GNUC_CONST; + +ScSecurityTokenMonitor *sc_security_token_monitor_new (const gchar *module); + +gboolean sc_security_token_monitor_start (ScSecurityTokenMonitor *monitor, + GError **error); + +void sc_security_token_monitor_stop (ScSecurityTokenMonitor *monitor); + +gchar *sc_security_token_monitor_get_module_path (ScSecurityTokenMonitor *monitor); +gboolean sc_security_token_monitor_login_token_is_inserted (ScSecurityTokenMonitor *monitor); + +G_END_DECLS +#endif /* SC_SECURITY_TOKEN_MONITOR_H */ --- gdm-2.19.1/daemon/Makefile.am.security-tokens 2007-05-13 22:08:25.000000000 -0400 +++ gdm-2.19.1/daemon/Makefile.am 2007-05-21 16:04:51.000000000 -0400 @@ -9,6 +9,7 @@ INCLUDES = \ -DAUTHDIR=\"$(authdir)\" \ -DBINDIR=\"$(bindir)\" \ -DDATADIR=\"$(datadir)\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ -DDMCONFDIR=\"$(dmconfdir)\" \ -DGDMCONFDIR=\"$(gdmconfdir)\" \ -DGDMLOCALEDIR=\"$(gdmlocaledir)\" \ @@ -68,6 +69,10 @@ gdm_binary_SOURCES = \ gdm-net.h \ getvt.c \ getvt.h \ + securitytoken.c \ + securitytoken.h \ + securitytokenmonitor.c \ + securitytokenmonitor.h \ $(NULL) XDMCP_SOURCES = \ --- /dev/null 2007-05-21 09:34:56.803421964 -0400 +++ gdm-2.19.1/daemon/securitytoken.h 2007-05-21 16:04:51.000000000 -0400 @@ -0,0 +1,94 @@ +/* securitytoken.h - api for reading and writing data to a security token + * + * 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. + */ +#ifndef SC_SECURITY_TOKEN_H +#define SC_SECURITY_TOKEN_H + +#include +#include + +#include + +G_BEGIN_DECLS +#define SC_TYPE_SECURITY_TOKEN (sc_security_token_get_type ()) +#define SC_SECURITY_TOKEN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SC_TYPE_SECURITY_TOKEN, ScSecurityToken)) +#define SC_SECURITY_TOKEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SC_TYPE_SECURITY_TOKEN, ScSecurityTokenClass)) +#define SC_IS_SECURITY_TOKEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SC_TYPE_SECURITY_TOKEN)) +#define SC_IS_SECURITY_TOKEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SC_TYPE_SECURITY_TOKEN)) +#define SC_SECURITY_TOKEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SC_TYPE_SECURITY_TOKEN, ScSecurityTokenClass)) +#define SC_SECURITY_TOKEN_ERROR (sc_security_token_error_quark ()) +typedef struct _ScSecurityTokenClass ScSecurityTokenClass; +typedef struct _ScSecurityToken ScSecurityToken; +typedef struct _ScSecurityTokenPrivate ScSecurityTokenPrivate; +typedef enum _ScSecurityTokenError ScSecurityTokenError; +typedef enum _ScSecurityTokenState ScSecurityTokenState; + +typedef struct _ScSecurityTokenRequest ScSecurityTokenRequest; + +struct _ScSecurityToken { + GObject parent; + + /*< private > */ + ScSecurityTokenPrivate *priv; +}; + +struct _ScSecurityTokenClass { + GObjectClass parent_class; + + void (* inserted) (ScSecurityToken *token); + void (* removed) (ScSecurityToken *token); +}; + +enum _ScSecurityTokenError { + SC_SECURITY_TOKEN_ERROR_GENERIC = 0, +}; + +enum _ScSecurityTokenState { + SC_SECURITY_TOKEN_STATE_INSERTED = 0, + SC_SECURITY_TOKEN_STATE_REMOVED, +}; + +GType sc_security_token_get_type (void) G_GNUC_CONST; +GQuark sc_security_token_error_quark (void) G_GNUC_CONST; + +CK_SLOT_ID sc_security_token_get_slot_id (ScSecurityToken *token); +gint sc_security_token_get_slot_series (ScSecurityToken *token); +ScSecurityTokenState sc_security_token_get_state (ScSecurityToken *token); + +gchar *sc_security_token_get_name (ScSecurityToken *token); +gboolean sc_security_token_is_login_token (ScSecurityToken *token); + +gboolean sc_security_token_unlock (ScSecurityToken *token, + const gchar *password); + +/* don't under any circumstances call these functions */ +#ifdef SC_SECURITY_TOKEN_ENABLE_INTERNAL_API + +ScSecurityToken *_sc_security_token_new (SECMODModule *module, + CK_SLOT_ID slot_id, + gint slot_series); +ScSecurityToken *_sc_security_token_new_from_name (SECMODModule *module, + const gchar *name); + +void _sc_security_token_set_state (ScSecurityToken *token, + ScSecurityTokenState state); +#endif + +G_END_DECLS +#endif /* SC_SECURITY_TOKEN_H */ --- /dev/null 2007-05-21 09:34:56.803421964 -0400 +++ gdm-2.19.1/daemon/securitytokenmonitor.c 2007-05-21 16:04:51.000000000 -0400 @@ -0,0 +1,1743 @@ +/* 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. + * + * TODO: - doing this per project is a bad idea i think. + * We should probably make this a system service + * and use dbus. + */ +#define _GNU_SOURCE +#include "securitytokenmonitor.h" + +#define SC_SECURITY_TOKEN_ENABLE_INTERNAL_API +#include "securitytoken.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef SC_SECURITY_TOKEN_MONITOR_DRIVER +#define SC_SECURITY_TOKEN_MONITOR_DRIVER LIBDIR"/pkcs11/libcoolkeypk11.so" +#endif + +#ifndef SC_SECURITY_TOKEN_MONITOR_NSS_DB +#define SC_SECURITY_TOKEN_MONITOR_NSS_DB SYSCONFDIR"/pki/nssdb" +#endif + +#ifndef SC_MAX_OPEN_FILE_DESCRIPTORS +#define SC_MAX_OPEN_FILE_DESCRIPTORS 1024 +#endif + +#ifndef SC_OPEN_FILE_DESCRIPTORS_DIR +#define SC_OPEN_FILE_DESCRIPTORS_DIR "/proc/self/fd" +#endif + +#ifndef sc_debug +#if defined (SC_SECURITY_TOKEN_MONITOR_ENABLE_TEST) +#define sc_debug(fmt, args...) g_printerr("[%u] " fmt " \n", getpid(), ##args) +#else +#define sc_debug(fmt, args...) +#endif +#endif + +typedef enum _ScSecurityTokenMonitorState ScSecurityTokenMonitorState; +typedef struct _ScSecurityTokenMonitorWorker ScSecurityTokenMonitorWorker; + +enum _ScSecurityTokenMonitorState { + SC_SECURITY_TOKEN_MONITOR_STATE_STOPPED = 0, + SC_SECURITY_TOKEN_MONITOR_STATE_STARTING, + SC_SECURITY_TOKEN_MONITOR_STATE_STARTED, + SC_SECURITY_TOKEN_MONITOR_STATE_STOPPING, +}; + +struct _ScSecurityTokenMonitorPrivate { + ScSecurityTokenMonitorState state; + SECMODModule *module; + gchar *module_path; + + GSource *security_token_event_source; + GPid security_token_event_watcher_pid; + GHashTable *security_tokens; + + guint poll_timeout_id; + + guint32 is_unstoppable : 1; + guint32 nss_is_loaded : 1; + +#ifndef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + GArray *fds_to_close_on_fork; +#endif +}; + +struct _ScSecurityTokenMonitorWorker { + SECMODModule *module; + GHashTable *security_tokens; + gint write_fd; + + guint32 nss_is_loaded : 1; +}; + +static void sc_security_token_monitor_finalize (GObject *object); +static void sc_security_token_monitor_class_install_signals (ScSecurityTokenMonitorClass *service_class); +static void sc_security_token_monitor_class_install_properties (ScSecurityTokenMonitorClass *service_class); +static void sc_security_token_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void sc_security_token_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void sc_security_token_monitor_set_module_path (ScSecurityTokenMonitor *monitor, + const gchar *module_path); +static void sc_security_token_monitor_token_removed_handler (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token); +static void sc_security_token_monitor_token_inserted_handler (ScSecurityTokenMonitor *monitor_class, + ScSecurityToken *token); +static gboolean sc_security_token_monitor_stop_now (ScSecurityTokenMonitor *monitor); +static void sc_security_token_monitor_queue_stop (ScSecurityTokenMonitor *monitor); + +static gboolean sc_security_token_monitor_create_worker (ScSecurityTokenMonitor *monitor, + gint *worker_fd, GPid *worker_pid); + +static ScSecurityTokenMonitorWorker * sc_security_token_monitor_worker_new (gint write_fd); +static void sc_security_token_monitor_worker_free (ScSecurityTokenMonitorWorker *worker); +static void sc_security_token_monitor_worker_die_with_parent (ScSecurityTokenMonitorWorker *worker); +static gboolean sc_open_pipe (gint *write_fd, gint *read_fd); +static gboolean sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes); +static gboolean sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes); +static ScSecurityToken *sc_read_security_token (gint fd, SECMODModule *module); +static gboolean sc_write_security_token (gint fd, ScSecurityToken *token); + +enum { + PROP_0 = 0, + PROP_MODULE_PATH, + NUMBER_OF_PROPERTIES +}; + +enum { + SECURITY_TOKEN_INSERTED = 0, + SECURITY_TOKEN_REMOVED, + ERROR, + NUMBER_OF_SIGNALS +}; + +static guint sc_security_token_monitor_signals[NUMBER_OF_SIGNALS]; + +G_DEFINE_TYPE (ScSecurityTokenMonitor, + sc_security_token_monitor, + G_TYPE_OBJECT); + +static void +sc_security_token_monitor_class_init (ScSecurityTokenMonitorClass *monitor_class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (monitor_class); + + gobject_class->finalize = sc_security_token_monitor_finalize; + + sc_security_token_monitor_class_install_signals (monitor_class); + sc_security_token_monitor_class_install_properties (monitor_class); + + g_type_class_add_private (monitor_class, + sizeof (ScSecurityTokenMonitorPrivate)); +} + +static void +sc_security_token_monitor_class_install_properties (ScSecurityTokenMonitorClass *token_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (token_class); + object_class->set_property = sc_security_token_monitor_set_property; + object_class->get_property = sc_security_token_monitor_get_property; + + param_spec = g_param_spec_string ("module-path", _("Module Path"), + _("path to security token PKCS #11 driver"), + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_MODULE_PATH, param_spec); +} + +static void +sc_security_token_monitor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ScSecurityTokenMonitor *monitor = SC_SECURITY_TOKEN_MONITOR (object); + + switch (prop_id) + { + case PROP_MODULE_PATH: + sc_security_token_monitor_set_module_path (monitor, + g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +sc_security_token_monitor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ScSecurityTokenMonitor *monitor = SC_SECURITY_TOKEN_MONITOR (object); + gchar *module_path; + + switch (prop_id) + { + case PROP_MODULE_PATH: + module_path = sc_security_token_monitor_get_module_path (monitor); + g_value_set_string (value, module_path); + g_free (module_path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gchar * +sc_security_token_monitor_get_module_path (ScSecurityTokenMonitor *monitor) +{ + return monitor->priv->module_path; +} + +static void +sc_security_token_monitor_set_module_path (ScSecurityTokenMonitor *monitor, + const gchar *module_path) +{ + if ((monitor->priv->module_path == NULL) && (module_path == NULL)) + return; + + if (((monitor->priv->module_path == NULL) || + (module_path == NULL) || + (strcmp (monitor->priv->module_path, module_path) != 0))) { + g_free (monitor->priv->module_path); + monitor->priv->module_path = g_strdup (module_path); + g_object_notify (G_OBJECT (monitor), "module-path"); + } +} + +static void +sc_security_token_monitor_token_removed_handler (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token) +{ + sc_debug ("informing security token of its removal"); + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_REMOVED); + sc_debug ("done"); +} + +static void +sc_security_token_monitor_token_inserted_handler (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token) +{ + sc_debug ("informing security token of its insertion"); + + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_INSERTED); + sc_debug ("done"); + +} + +static void +sc_security_token_monitor_class_install_signals (ScSecurityTokenMonitorClass *monitor_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (monitor_class); + + sc_security_token_monitor_signals[SECURITY_TOKEN_INSERTED] = + g_signal_new ("security-token-inserted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ScSecurityTokenMonitorClass, + security_token_inserted), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + monitor_class->security_token_inserted = sc_security_token_monitor_token_inserted_handler; + + sc_security_token_monitor_signals[SECURITY_TOKEN_REMOVED] = + g_signal_new ("security-token-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ScSecurityTokenMonitorClass, + security_token_removed), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + monitor_class->security_token_removed = sc_security_token_monitor_token_removed_handler; + + sc_security_token_monitor_signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ScSecurityTokenMonitorClass, error), + NULL, NULL, g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + monitor_class->error = NULL; +} + +static gboolean +sc_slot_id_equal (CK_SLOT_ID *slot_id_1, + CK_SLOT_ID *slot_id_2) +{ + g_assert (slot_id_1 != NULL); + g_assert (slot_id_2 != NULL); + + return *slot_id_1 == *slot_id_2; +} + +static gboolean +sc_slot_id_hash (CK_SLOT_ID *slot_id) +{ + guint32 upper_bits, lower_bits; + gint temp; + + if (sizeof (CK_SLOT_ID) == sizeof (gint)) + return g_int_hash (slot_id); + + upper_bits = ((*slot_id) >> 31) - 1; + lower_bits = (*slot_id) & 0xffffffff; + + /* The upper bits are almost certainly always zero, + * so let's degenerate to g_int_hash for the + * (very) common case + */ + temp = lower_bits + upper_bits; + return upper_bits + g_int_hash (&temp); +} + +static void +sc_security_token_monitor_init (ScSecurityTokenMonitor *monitor) +{ + sc_debug ("initializing security token monitor"); + + monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, + SC_TYPE_SECURITY_TOKEN_MONITOR, + ScSecurityTokenMonitorPrivate); + monitor->priv->poll_timeout_id = 0; + monitor->priv->is_unstoppable = FALSE; + monitor->priv->module = NULL; + + monitor->priv->security_tokens = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + +#ifndef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + monitor->priv->fds_to_close_on_fork = g_array_new (FALSE, FALSE, sizeof (gint)); +#endif + +} + +static void +sc_security_token_monitor_finalize (GObject *object) +{ + ScSecurityTokenMonitor *monitor; + GObjectClass *gobject_class; + + monitor = SC_SECURITY_TOKEN_MONITOR (object); + gobject_class = + G_OBJECT_CLASS (sc_security_token_monitor_parent_class); + + sc_security_token_monitor_stop_now (monitor); + + g_hash_table_destroy (monitor->priv->security_tokens); + monitor->priv->security_tokens = NULL; + +#ifndef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + g_array_free (monitor->priv->fds_to_close_on_fork, TRUE); +#endif + + gobject_class->finalize (object); +} + +GQuark +sc_security_token_monitor_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) + error_quark = g_quark_from_static_string ("sc-security-token-monitor-error-quark"); + + return error_quark; +} + +ScSecurityTokenMonitor * +sc_security_token_monitor_new (const gchar *module_path) +{ + ScSecurityTokenMonitor *instance; + + instance = SC_SECURITY_TOKEN_MONITOR (g_object_new (SC_TYPE_SECURITY_TOKEN_MONITOR, + "module-path", module_path, + NULL)); + + return instance; +} + +static void +sc_security_token_monitor_emit_error (ScSecurityTokenMonitor *monitor, + GError *error) +{ + monitor->priv->is_unstoppable = TRUE; + g_signal_emit (monitor, sc_security_token_monitor_signals[ERROR], 0, + error); + monitor->priv->is_unstoppable = FALSE; +} + +static void +sc_security_token_monitor_emit_security_token_inserted (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token) +{ + monitor->priv->is_unstoppable = TRUE; + g_signal_emit (monitor, sc_security_token_monitor_signals[SECURITY_TOKEN_INSERTED], 0, + token); + monitor->priv->is_unstoppable = FALSE; +} + +static void +sc_security_token_monitor_emit_security_token_removed (ScSecurityTokenMonitor *monitor, + ScSecurityToken *token) +{ + ScSecurityTokenMonitorState old_state; + + old_state = monitor->priv->state; + monitor->priv->is_unstoppable = TRUE; + g_signal_emit (monitor, sc_security_token_monitor_signals[SECURITY_TOKEN_REMOVED], 0, + token); + monitor->priv->is_unstoppable = FALSE; +} + +static gboolean +sc_security_token_monitor_check_for_and_process_events (GIOChannel *io_channel, + GIOCondition condition, + ScSecurityTokenMonitor *monitor) +{ + ScSecurityToken *token; + gboolean should_stop; + guchar event_type; + gchar *token_name; + gint fd; + + token = NULL; + should_stop = (condition & G_IO_HUP) || (condition & G_IO_ERR); + + if (should_stop) + sc_debug ("received %s on event socket, stopping " + "monitor...", + (condition & G_IO_HUP) && (condition & G_IO_ERR)? + "error and hangup" : + (condition & G_IO_HUP)? + "hangup" : "error"); + + if (!(condition & G_IO_IN)) + goto out; + + fd = g_io_channel_unix_get_fd (io_channel); + + event_type = '\0'; + if (!sc_read_bytes (fd, &event_type, 1)) { + should_stop = TRUE; + goto out; + } + + token = sc_read_security_token (fd, monitor->priv->module); + + if (token == NULL) { + should_stop = TRUE; + goto out; + } + + token_name = sc_security_token_get_name (token); + + switch (event_type) { + case 'I': + g_hash_table_replace (monitor->priv->security_tokens, + token_name, token); + token_name = NULL; + + sc_security_token_monitor_emit_security_token_inserted (monitor, token); + token = NULL; + break; + + case 'R': + sc_security_token_monitor_emit_security_token_removed (monitor, token); + if (!g_hash_table_remove (monitor->priv->security_tokens, token_name)) + sc_debug ("got removal event of unknown token!"); + g_free (token_name); + token_name = NULL; + token = NULL; + break; + + default: + g_free (token_name); + token_name = NULL; + g_object_unref (token); + + should_stop = TRUE; + break; + } + +out: + if (should_stop) { + GError *error; + + error = g_error_new (SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_WATCHING_FOR_EVENTS, + "%s", (condition & G_IO_IN) ? g_strerror (errno) : _("received error or hang up from event source")); + + sc_security_token_monitor_emit_error (monitor, error); + g_error_free (error); + sc_security_token_monitor_stop_now (monitor); + return FALSE; + } + + return TRUE; +} + +static void +sc_security_token_monitor_event_processing_stopped_handler (ScSecurityTokenMonitor *monitor) +{ + monitor->priv->security_token_event_source = NULL; + sc_security_token_monitor_stop_now (monitor); +} + +/* sorta complex function that is nothing more than fork() without having + * to worry about reaping the child later with waitpid + */ +static GPid +sc_fork_and_disown (void) +{ + pid_t child_pid; + GPid grandchild_pid; + gint write_fd, read_fd; + gint saved_errno; + + write_fd = -1; + read_fd = -1; + if (!sc_open_pipe (&write_fd, &read_fd)) + return (GPid) -1; + + child_pid = fork (); + + if (child_pid < 0) { + close (write_fd); + close (read_fd); + return (GPid) child_pid; + } + + if (child_pid == 0) { + + /* close the end of the pipe we're not going to use + */ + close (read_fd); + + /* fork again + */ + child_pid = fork (); + + /* in the event of error, write out negative errno + */ + if (child_pid < 0) { + child_pid = -1 * errno; + + sc_write_bytes (write_fd, &child_pid, sizeof (child_pid)); + close (write_fd); + _exit (1); + } + + /* otherwise write out the pid of the child and exit + */ + if (child_pid != 0) { + + signal (SIGPIPE, SIG_IGN); + + if (!sc_write_bytes (write_fd, &child_pid, sizeof (child_pid))) { + kill (SIGKILL, child_pid); + _exit (2); + } + close (write_fd); + _exit (0); + } + close (write_fd); + + /* we're done, we've forked without having to worry about + * reaping the child later + */ + g_assert (child_pid == 0); + return (GPid) 0; + } + + /* close the end of the pipe we're not going to use + */ + close (write_fd); + + grandchild_pid = -1; + if (!sc_read_bytes (read_fd, &grandchild_pid, sizeof (grandchild_pid))) { + grandchild_pid = -1; + } + + saved_errno = errno; + + /* close the other end of the pipe since we're done with it + */ + close (read_fd); + + /* wait for child to die (and emancipate the grandchild) + */ + waitpid (child_pid, NULL, 0); + + errno = saved_errno; + return (GPid) grandchild_pid; +} + +static gboolean +sc_open_pipe (gint *write_fd, + gint *read_fd) +{ + gint pipe_fds[2] = { -1, -1 }; + + g_assert (write_fd != NULL); + g_assert (read_fd != NULL); + + if (pipe (pipe_fds) < 0) + return FALSE; + + if (fcntl (pipe_fds[0], F_SETFD, FD_CLOEXEC) < 0) { + close (pipe_fds[0]); + close (pipe_fds[1]); + return FALSE; + } + + if (fcntl (pipe_fds[1], F_SETFD, FD_CLOEXEC) < 0) { + close (pipe_fds[0]); + close (pipe_fds[1]); + return FALSE; + } + + *read_fd = pipe_fds[0]; + *write_fd = pipe_fds[1]; + + return TRUE; +} + +static void +sc_security_token_monitor_stop_watching_for_events (ScSecurityTokenMonitor *monitor) +{ + if (monitor->priv->security_token_event_source != NULL) { + g_source_destroy (monitor->priv->security_token_event_source); + monitor->priv->security_token_event_source = NULL; + } + + if (monitor->priv->security_token_event_watcher_pid > 0) { + kill (monitor->priv->security_token_event_watcher_pid, SIGKILL); + monitor->priv->security_token_event_watcher_pid = 0; + } +} + +static gboolean +sc_load_nss (GError **error) +{ + SECStatus status = SECSuccess; + static const guint32 flags = + NSS_INIT_READONLY| NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | + NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT | + NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD; + + sc_debug ("attempting to load NSS database '%s'", + SC_SECURITY_TOKEN_MONITOR_NSS_DB); + + status = NSS_Initialize (SC_SECURITY_TOKEN_MONITOR_NSS_DB, + "", "", SECMOD_DB, flags); + + if (status != SECSuccess) { + gsize error_message_size; + gchar *error_message; + + error_message_size = PR_GetErrorTextLength (); + + if (error_message_size == 0) { + sc_debug ("NSS security system could not be initialized"); + g_set_error (error, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_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, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, + "%s", error_message); + sc_debug ("NSS security system could not be initialized - %s", + error_message); + + g_slice_free1 (error_message_size, error_message); + + goto out; + } + + sc_debug ("NSS database sucessfully loaded"); + return TRUE; + +out: + sc_debug ("NSS database couldn't be sucessfully loaded"); + return FALSE; +} + +static SECMODModule * +sc_load_driver (gchar *module_path, + GError **error) +{ + SECMODModule *module; + gchar *module_spec; + gboolean module_explicitly_specified; + + sc_debug ("attempting to load driver..."); + + module = NULL; + module_explicitly_specified = module_path != NULL; + if (module_explicitly_specified) { + module_spec = g_strdup_printf ("library=\"%s\"", module_path); + sc_debug ("loading security token driver using spec '%s'", + module_spec); + + module = SECMOD_LoadUserModule (module_spec, + NULL /* parent */, + FALSE /* recurse */); + g_free (module_spec); + module_spec = NULL; + + } else { + SECMODModuleList *modules, *tmp; + + modules = SECMOD_GetDefaultModuleList (); + + for (tmp = modules; tmp != NULL; tmp = tmp->next) { + if (!SECMOD_HasRemovableSlots (tmp->module) || + !tmp->module->loaded) + continue; + + module = SECMOD_ReferenceModule (tmp->module); + break; + } + + /* fallback to compiled in driver path + */ + if (module == NULL) { + if (g_file_test (SC_SECURITY_TOKEN_MONITOR_DRIVER, + G_FILE_TEST_IS_REGULAR)) { + + module_spec = g_strdup_printf ("library=\"%s\"", module_path); + sc_debug ("loading security token driver using spec '%s'", + module_spec); + + module = SECMOD_LoadUserModule (module_spec, + NULL /* parent */, + FALSE /* recurse */); + g_free (module_spec); + module_spec = NULL; + + } + } + + } + + if (!module_explicitly_specified && module == NULL) { + g_set_error (error, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, + _("no suitable security token driver could be found")); + } else if (module == NULL || !module->loaded) { + + gsize error_message_size; + gchar *error_message; + + if (module != NULL && !module->loaded) { + sc_debug ("module found but not loaded?!"); + SECMOD_DestroyModule (module); + module = NULL; + } + + error_message_size = PR_GetErrorTextLength (); + + if (error_message_size == 0) { + sc_debug ("security token driver '%s' could not be loaded", + module_path); + g_set_error (error, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, + _("security token driver '%s' could not be " + "loaded"), module_path); + goto out; + } + + error_message = g_slice_alloc0 (error_message_size); + PR_GetErrorText (error_message); + + g_set_error (error, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_LOADING_DRIVER, + "%s", error_message); + + sc_debug ("security token driver '%s' could not be loaded - %s", + module_path, error_message); + g_slice_free1 (error_message_size, error_message); + } + +out: + return module; +} + +static void +sc_security_token_monitor_get_all_tokens (ScSecurityTokenMonitor *monitor) +{ + int i; + + for (i = 0; i < monitor->priv->module->slotCount; i++) { + ScSecurityToken *token; + CK_SLOT_ID slot_id; + gint slot_series; + gchar *token_name; + + slot_id = PK11_GetSlotID (monitor->priv->module->slots[i]); + slot_series = PK11_GetSlotSeries (monitor->priv->module->slots[i]); + + token = _sc_security_token_new (monitor->priv->module, + slot_id, slot_series); + + token_name = sc_security_token_get_name (token); + + g_hash_table_replace (monitor->priv->security_tokens, + token_name, token); + } +} + +gboolean +sc_security_token_monitor_start (ScSecurityTokenMonitor *monitor, + GError **error) +{ + GError *watching_error; + gint worker_fd; + GPid worker_pid; + GIOChannel *io_channel; + GSource *source; + GIOFlags channel_flags; + GError *nss_error; + + if (monitor->priv->state == SC_SECURITY_TOKEN_MONITOR_STATE_STARTED) { + sc_debug ("security token monitor already started"); + return TRUE; + } + + monitor->priv->state = SC_SECURITY_TOKEN_MONITOR_STATE_STARTING; + + worker_fd = -1; + worker_pid = 0; + + nss_error = NULL; + if (!monitor->priv->nss_is_loaded && !sc_load_nss (&nss_error)) { + g_propagate_error (error, nss_error); + goto out; + } + monitor->priv->nss_is_loaded = TRUE; + + if (monitor->priv->module == NULL) + monitor->priv->module = sc_load_driver (monitor->priv->module_path, &nss_error); + + if (monitor->priv->module == NULL) { + g_propagate_error (error, nss_error); + goto out; + } + + if (!sc_security_token_monitor_create_worker (monitor, &worker_fd, &worker_pid)) { + + g_set_error (error, + SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_WATCHING_FOR_EVENTS, + _("could not watch for incoming token events - %s"), + g_strerror (errno)); + + goto out; + } + + monitor->priv->security_token_event_watcher_pid = worker_pid; + + io_channel = g_io_channel_unix_new (worker_fd); + + channel_flags = g_io_channel_get_flags (io_channel); + watching_error = NULL; + + source = g_io_create_watch (io_channel, G_IO_IN | G_IO_HUP); + g_io_channel_unref (io_channel); + io_channel = NULL; + + monitor->priv->security_token_event_source = source; + + g_source_set_callback (monitor->priv->security_token_event_source, + (GSourceFunc) (GIOFunc) + sc_security_token_monitor_check_for_and_process_events, + monitor, + (GDestroyNotify) + sc_security_token_monitor_event_processing_stopped_handler); + g_source_attach (monitor->priv->security_token_event_source, NULL); + g_source_unref (monitor->priv->security_token_event_source); + + /* populate the hash with tokens that are already inserted + */ + sc_security_token_monitor_get_all_tokens (monitor); + + monitor->priv->state = SC_SECURITY_TOKEN_MONITOR_STATE_STARTED; + +out: + /* don't leave it in a half started state + */ + if (monitor->priv->state != SC_SECURITY_TOKEN_MONITOR_STATE_STARTED) { + sc_debug ("security token monitor could not be completely started"); + sc_security_token_monitor_stop (monitor); + } else + sc_debug ("security token monitor started"); + + return monitor->priv->state == SC_SECURITY_TOKEN_MONITOR_STATE_STARTED; +} + +static gboolean +sc_security_token_monitor_stop_now (ScSecurityTokenMonitor *monitor) +{ + if (monitor->priv->state == SC_SECURITY_TOKEN_MONITOR_STATE_STOPPED) + return FALSE; + + monitor->priv->state = SC_SECURITY_TOKEN_MONITOR_STATE_STOPPED; + sc_security_token_monitor_stop_watching_for_events (monitor); +#ifdef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + 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; + } +#endif + sc_debug ("security token monitor stopped"); + + return FALSE; +} + +static void +sc_security_token_monitor_queue_stop (ScSecurityTokenMonitor *monitor) +{ + + monitor->priv->state = SC_SECURITY_TOKEN_MONITOR_STATE_STOPPING; + + g_idle_add ((GSourceFunc) sc_security_token_monitor_stop_now, monitor); +} + +void +sc_security_token_monitor_stop (ScSecurityTokenMonitor *monitor) +{ + if (monitor->priv->state == SC_SECURITY_TOKEN_MONITOR_STATE_STOPPED) + return; + + if (monitor->priv->is_unstoppable) { + sc_security_token_monitor_queue_stop (monitor); + return; + } + + sc_security_token_monitor_stop_now (monitor); +} + +static void +sc_security_token_monitor_check_for_login_token (CK_SLOT_ID slot_id, + ScSecurityToken *token, + gboolean *is_inserted) +{ + g_assert (is_inserted != NULL); + + if (sc_security_token_is_login_token (token)) + *is_inserted = TRUE; + +} + +gboolean +sc_security_token_monitor_login_token_is_inserted (ScSecurityTokenMonitor *monitor) + +{ + gboolean is_inserted; + + is_inserted = FALSE; + g_hash_table_foreach (monitor->priv->security_tokens, + (GHFunc) + sc_security_token_monitor_check_for_login_token, + &is_inserted); + return is_inserted; +} + +static gint +sc_get_max_open_fds (void) +{ + struct rlimit open_fd_limit; + const gint fallback_limit = SC_MAX_OPEN_FILE_DESCRIPTORS; + + if (getrlimit (RLIMIT_NOFILE, &open_fd_limit) < 0) { + sc_debug ("could not get file descriptor limit: %s", + g_strerror (errno)); + sc_debug ("returning fallback file descriptor limit of %d", + fallback_limit); + return fallback_limit; + } + + if (open_fd_limit.rlim_cur == RLIM_INFINITY) { + sc_debug ("currently no file descriptor limit, returning fallback limit of %d", + fallback_limit); + return fallback_limit; + } + + return (gint) open_fd_limit.rlim_cur; +} + +static void +sc_close_all_fds (int *fds_to_keep_open) +{ + int max_open_fds, fd; + + sc_debug ("closing all file descriptors"); + max_open_fds = sc_get_max_open_fds (); + + for (fd = 0; fd < max_open_fds; fd++) { + int i; + gboolean should_close_fd; + + should_close_fd = TRUE; + + if (fds_to_keep_open != NULL) { + for (i = 0; fds_to_keep_open[i] >= 0; i++) { + if (fd == fds_to_keep_open[i]) { + should_close_fd = FALSE; + break; + } + } + } + + if (should_close_fd) { + sc_debug ("closing file descriptor '%d'", fd); + close (fd); + } + } +} + +static void +sc_close_open_fds (int *fds_to_keep_open) +{ + /* using DIR instead of GDir because we need access to dirfd so + * that we can iterate through the fds and close them in one sweep. + * (if we just closed all of them then we would close the one we're using + * for reading the directory!) + */ + DIR *dir; + struct dirent *entry; + gint fd, opendir_fd; + gboolean should_use_fallback; + + should_use_fallback = FALSE; + opendir_fd = -1; + + dir = opendir (SC_OPEN_FILE_DESCRIPTORS_DIR); + + if (dir != NULL) + opendir_fd = dirfd (dir); + + if ((dir == NULL) || (opendir_fd < 0)) { + sc_debug ("could not open "SC_OPEN_FILE_DESCRIPTORS_DIR": %s", g_strerror (errno)); + should_use_fallback = TRUE; + } else { + sc_debug ("reading files in '"SC_OPEN_FILE_DESCRIPTORS_DIR"'"); + while ((entry = readdir (dir)) != NULL) { + gint i; + glong filename_as_number; + gchar *byte_after_number; + gboolean should_close_fd; + + errno = 0; + if (entry->d_name[0] == '.') + continue; + + sc_debug ("scanning filename '%s' for file descriptor number", + entry->d_name); + fd = -1; + filename_as_number = strtol (entry->d_name, &byte_after_number, 10); + + g_assert (byte_after_number != NULL); + + if ((*byte_after_number != '\0') || + (filename_as_number < 0) || + (filename_as_number >= G_MAXINT)) { + sc_debug ("filename '%s' does not appear to represent a " + "file descriptor: %s", + entry->d_name, strerror (errno)); + should_use_fallback = TRUE; + } else { + fd = (gint) filename_as_number; + sc_debug ("filename '%s' represents file descriptor '%d'", + entry->d_name, fd); + should_use_fallback = FALSE; + } + + if (fd == opendir_fd) { + should_close_fd = FALSE; + } else { + should_close_fd = TRUE; + if (fds_to_keep_open != NULL) + for (i = 0; fds_to_keep_open[i] >= 0; i++) { + if (fd == fds_to_keep_open[i]) { + should_close_fd = FALSE; + break; + } + } + } + + if (should_close_fd) { + sc_debug ("closing file descriptor '%d'", fd); + close (fd); + } else { + sc_debug ("will not close file descriptor '%d' because it " + "is still needed", fd); + } + } + + if (entry != NULL) + should_use_fallback = TRUE; + + sc_debug ("closing directory '"SC_OPEN_FILE_DESCRIPTORS_DIR"'"); + closedir (dir); + } + + /* if /proc isn't mounted or something else is screwy, + * fall back to closing everything + */ + if (should_use_fallback) + sc_close_all_fds (fds_to_keep_open); +} + +static void +sc_close_fds (gint *fds, + gint num_fds) +{ + gint i; + + for (i = 0; i < num_fds; i++) + close (fds[i]); +} + +static ScSecurityTokenMonitorWorker * +sc_security_token_monitor_worker_new (gint write_fd) +{ + ScSecurityTokenMonitorWorker *worker; + + worker = g_slice_new0 (ScSecurityTokenMonitorWorker); + worker->write_fd = write_fd; + worker->module = NULL; + + worker->security_tokens = + g_hash_table_new_full ((GHashFunc) sc_slot_id_hash, + (GEqualFunc) sc_slot_id_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + return worker; +} + +static void +sc_security_token_monitor_worker_free (ScSecurityTokenMonitorWorker *worker) +{ + if (worker->security_tokens != NULL) { + g_hash_table_destroy (worker->security_tokens); + worker->security_tokens = NULL; + } + + g_slice_free (ScSecurityTokenMonitorWorker, worker); +} + +/* This function checks to see if the helper's connection to the + * parent process has been closed. If it has, we assume the + * parent has died (or is otherwise done with the connection) + * and so we die, too. We do this from a signal handler (yuck!) + * because there isn't a nice way to cancel the + * SECMOD_WaitForAnyTokenEvent call, which just sits and blocks + * indefinitely. There is a SECMOD_CancelWait wait function + * that we could call if we would have gone multithreaded like + * NSS really wants us to do, but that call isn't signal handler + * safe, so we just _exit() instead (eww). + */ +static void +worker_io_signal_handler (int signal_number, + siginfo_t *signal_info, + void *data) +{ + int number_of_events; + int old_errno; + struct pollfd poll_fds[1] = { { 0 } }; + int parent_fd; + + old_errno = errno; + + /* pipe fd set up to talk to the parent */ + parent_fd = signal_info->si_fd; + + /* We only care about disconnection events + * (which get unmasked implicitly), so we just + * pass 0 for the event mask + */ + poll_fds[0].events = 0; + poll_fds[0].fd = parent_fd; + + do { + number_of_events = poll (poll_fds, G_N_ELEMENTS (poll_fds), 0); + } while ((number_of_events < 0) && (errno == EINTR)); + + g_assert (number_of_events <= G_N_ELEMENTS (poll_fds)); + + if (number_of_events < 0) + _exit (errno); + + /* pipe disconnected; parent died + */ + if (number_of_events > 0) { + g_assert (!(poll_fds[0].revents & POLLNVAL)); + + if ((poll_fds[0].revents & POLLHUP) || + (poll_fds[0].revents & POLLERR)) { + _exit (poll_fds[0].revents); + } + } + + errno = old_errno; +} + +static void +sc_security_token_monitor_worker_die_with_parent (ScSecurityTokenMonitorWorker *worker) +{ + struct sigaction action = { { 0 } }; + gint flags; + + /* dirty hack to clean up worker if parent goes away + */ + sigemptyset (&action.sa_mask); + action.sa_sigaction = worker_io_signal_handler; + action.sa_flags = SA_SIGINFO; + sigaction (SIGIO, &action, NULL); + + flags = fcntl (worker->write_fd, F_GETFL, 0); + + fcntl (worker->write_fd, F_SETOWN, getpid ()); + fcntl (worker->write_fd, F_SETFL, flags | O_ASYNC); + fcntl (worker->write_fd, F_SETSIG, SIGIO); +} + +static gboolean +sc_read_bytes (gint fd, gpointer bytes, gsize num_bytes) +{ + size_t bytes_left; + size_t total_bytes_read; + ssize_t bytes_read; + + bytes_left = (size_t) num_bytes; + total_bytes_read = 0; + + do { + bytes_read = read (fd, bytes + total_bytes_read, bytes_left); + g_assert (bytes_read <= (ssize_t) bytes_left); + + if (bytes_read <= 0) { + if ((bytes_read < 0) && (errno == EINTR || errno == EAGAIN)) + continue; + + bytes_left = 0; + } else { + bytes_left -= bytes_read; + total_bytes_read += bytes_read; + } + } while (bytes_left > 0); + + if (total_bytes_read < (size_t) num_bytes) + return FALSE; + + return TRUE; +} + +static gboolean +sc_write_bytes (gint fd, gconstpointer bytes, gsize num_bytes) +{ + size_t bytes_left; + size_t total_bytes_written; + ssize_t bytes_written; + + bytes_left = (size_t) num_bytes; + total_bytes_written = 0; + + do { + bytes_written = write (fd, bytes + total_bytes_written, bytes_left); + g_assert (bytes_written <= (ssize_t) bytes_left); + + if (bytes_written <= 0) { + if ((bytes_written < 0) && (errno == EINTR || errno == EAGAIN)) + continue; + + bytes_left = 0; + } else { + bytes_left -= bytes_written; + total_bytes_written += bytes_written; + } + } while (bytes_left > 0); + + if (total_bytes_written < (size_t) num_bytes) + return FALSE; + + return TRUE; +} + +static ScSecurityToken * +sc_read_security_token (gint fd, SECMODModule *module) +{ + ScSecurityToken *token; + gchar *token_name; + gsize token_name_size; + + token_name_size = 0; + if (!sc_read_bytes (fd, &token_name_size, sizeof (token_name_size))) + return NULL; + + token_name = g_slice_alloc0 (token_name_size); + if (!sc_read_bytes (fd, token_name, token_name_size)) { + g_slice_free1 (token_name_size, token_name); + return NULL; + } + token = _sc_security_token_new_from_name (module, token_name); + g_slice_free1 (token_name_size, token_name); + + return token; +} + +static gboolean +sc_write_security_token (gint fd, + ScSecurityToken *token) +{ + gsize token_name_size; + gchar *token_name; + + token_name = sc_security_token_get_name (token); + token_name_size = strlen (token_name) + 1; + + if (!sc_write_bytes (fd, &token_name_size, sizeof (token_name_size))) { + g_free (token_name); + return FALSE; + } + + if (!sc_write_bytes (fd, token_name, token_name_size)) { + g_free (token_name); + return FALSE; + } + g_free (token_name); + + return TRUE; +} + +static gboolean +sc_security_token_monitor_worker_emit_security_token_removed (ScSecurityTokenMonitorWorker *worker, + ScSecurityToken *token, + GError **error) +{ + sc_debug ("token '%s' removed!", sc_security_token_get_name (token)); + + if (!sc_write_bytes (worker->write_fd, "R", 1)) + goto error_out; + + if (!sc_write_security_token (worker->write_fd, token)) + goto error_out; + + return TRUE; + +error_out: + g_set_error (error, SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_REPORTING_EVENTS, + "%s", g_strerror (errno)); + return FALSE; +} + +static gboolean +sc_security_token_monitor_worker_emit_security_token_inserted (ScSecurityTokenMonitorWorker *worker, + ScSecurityToken *token, + GError **error) +{ + GError *write_error; + + write_error = NULL; + sc_debug ("token '%s' inserted!", sc_security_token_get_name (token)); + if (!sc_write_bytes (worker->write_fd, "I", 1)) + goto error_out; + + if (!sc_write_security_token (worker->write_fd, token)) + goto error_out; + + return TRUE; + +error_out: + g_set_error (error, SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_REPORTING_EVENTS, + "%s", g_strerror (errno)); + return FALSE; +} + +static gboolean +sc_security_token_monitor_worker_watch_for_and_process_event (ScSecurityTokenMonitorWorker *worker, + GError **error) +{ + PK11SlotInfo *slot; + CK_SLOT_ID slot_id, *key; + gint slot_series, token_slot_series; + ScSecurityToken *token; + GError *processing_error; + + sc_debug ("waiting for token event"); + + /* FIXME: we return FALSE quite a bit in this function without cleaning up + * resources. By returning FALSE we're going to ultimately exit anyway, but + * we should still be tidier about things. + */ + + slot = SECMOD_WaitForAnyTokenEvent (worker->module, 0, PR_SecondsToInterval (1)); + processing_error = NULL; + + if (slot == NULL) { + int error_code; + + error_code = PORT_GetError (); + if ((error_code == 0) || (error_code == SEC_ERROR_NO_EVENT)) { + sc_debug ("spurrious event occurred"); + return TRUE; + } + + /* FIXME: is there a function to convert from a PORT error + * code to a translated string? + */ + g_set_error (error, SC_SECURITY_TOKEN_MONITOR_ERROR, + SC_SECURITY_TOKEN_MONITOR_ERROR_WITH_NSS, + _("encountered unexpected error while " + "waiting for security token events")); + 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. + */ + key = g_new (CK_SLOT_ID, 1); + *key = slot_id; + token = g_hash_table_lookup (worker->security_tokens, key); + + if (token != NULL) + token_slot_series = sc_security_token_get_slot_series (token); + + 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 (we don't want unpaired insertion events). + */ + if ((token != NULL) && + token_slot_series != slot_series) { + if (!sc_security_token_monitor_worker_emit_security_token_removed (worker, token, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } + + token = _sc_security_token_new (worker->module, + slot_id, slot_series); + + g_hash_table_replace (worker->security_tokens, + key, token); + key = NULL; + + if (!sc_security_token_monitor_worker_emit_security_token_inserted (worker, token, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } 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) + * + * Right now, i'm just doing it once. + */ + if ((slot_series - token_slot_series) > 1) { + + if (!sc_security_token_monitor_worker_emit_security_token_removed (worker, token, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + g_hash_table_remove (worker->security_tokens, key); + + token = _sc_security_token_new (worker->module, + slot_id, slot_series); + g_hash_table_replace (worker->security_tokens, + key, token); + key = NULL; + if (!sc_security_token_monitor_worker_emit_security_token_inserted (worker, token, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + } + + if (!sc_security_token_monitor_worker_emit_security_token_removed (worker, token, &processing_error)) { + g_propagate_error (error, processing_error); + return FALSE; + } + + g_hash_table_remove (worker->security_tokens, key); + token = NULL; + } else { + sc_debug ("got spurious remove event"); + } + } + + g_free (key); + PK11_FreeSlot (slot); + + return TRUE; +} + +static gboolean +sc_security_token_monitor_create_worker (ScSecurityTokenMonitor *monitor, + gint *worker_fd, GPid *worker_pid) +{ + GPid child_pid; + gint write_fd, read_fd; + + write_fd = -1; + read_fd = -1; + if (!sc_open_pipe (&write_fd, &read_fd)) + return FALSE; + + child_pid = sc_fork_and_disown (); + + if (child_pid < 0) + return FALSE; + + if (child_pid == 0) { + GError *error; + ScSecurityTokenMonitorWorker *worker; + +/* FIXME: Gotta figure out why this isn't working +*/ +#ifdef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + gint fds_to_keep_open[] = { -1, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, -1 }; + + SECMOD_DestroyModule (monitor->priv->module); + monitor->priv->module = NULL; + + NSS_Shutdown (); + + fds_to_keep_open[0] = write_fd; + sc_close_open_fds (fds_to_keep_open); + read_fd = -1; + + if (!sc_load_nss (&error)) { + sc_debug ("could not load nss - %s", error->message); + g_error_free (error); + _exit (1); + } +#else + g_array_append_val (monitor->priv->fds_to_close_on_fork, read_fd); + /* Junky workaround to keep from leaking fds + */ + sc_close_fds ((gint *) monitor->priv->fds_to_close_on_fork->data, + monitor->priv->fds_to_close_on_fork->len); +#endif + error = NULL; + + worker = sc_security_token_monitor_worker_new (write_fd); + + sc_security_token_monitor_worker_die_with_parent (worker); + + worker->module = sc_load_driver (monitor->priv->module_path, &error); + + if (worker->module == NULL) { + sc_debug ("could not load nss driver - %s", error->message); + g_error_free (error); + _exit (2); + } + + while (sc_security_token_monitor_worker_watch_for_and_process_event (worker, &error)); + + sc_debug ("could not process token event - %s", error->message); + sc_security_token_monitor_worker_free (worker); + + _exit (0); + } + + close (write_fd); + +#ifndef SC_SECURITY_TOKEN_MONITOR_DRIVER_CAN_BE_RELOADED_AFTER_BEING_DESTROYED + g_array_append_val (monitor->priv->fds_to_close_on_fork, read_fd); +#endif + + if (worker_pid) + *worker_pid = child_pid; + + if (worker_fd) + *worker_fd = read_fd; + + return TRUE; +} + +#ifdef SC_SECURITY_TOKEN_MONITOR_ENABLE_TEST +#include + +static GMainLoop *event_loop; +static gboolean should_exit_on_next_remove = FALSE; + +static gboolean +on_timeout (ScSecurityTokenMonitor *monitor) +{ + GError *error; + g_print ("Re-enabling monitor.\n"); + + if (!sc_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 (ScSecurityTokenMonitor * monitor, + ScSecurityToken *token) +{ + g_print ("security token inserted!\n"); + g_print ("Please remove it.\n"); +} + +static void +on_device_removed (ScSecurityTokenMonitor * monitor, + ScSecurityToken *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"); + sc_security_token_monitor_stop (monitor); + g_timeout_add (2000, (GSourceFunc) on_timeout, monitor); + } +} + +int +main (int argc, + char *argv[]) +{ + ScSecurityTokenMonitor *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 = sc_security_token_monitor_new (NULL); + 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 (!sc_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 --- /dev/null 2007-05-21 09:34:56.803421964 -0400 +++ gdm-2.19.1/daemon/securitytoken.c 2007-05-21 16:04:51.000000000 -0400 @@ -0,0 +1,680 @@ +/* securitytoken.c - security token + * + * 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. + * + * TODO: - doing this per project is a bad idea i think. + * We should probably make this a system service + * and use dbus. + * + * - We hardcode a driver right now. We should probably + * look up the default list and go from there. + */ +#define SC_SECURITY_TOKEN_ENABLE_INTERNAL_API +#include "securitytoken.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined (SC_SECURITY_TOKEN_ENABLE_TEST) || defined (SC_SECURITY_TOKEN_MONITOR_ENABLE_TEST) +#define sc_debug(format, args...) g_printerr (format "\n", ##args) +#define sc_warning(format, args...) g_printerr (format "\n", ##args) +#else +#define sc_debug(format, args...) +#define sc_warning(format, args...) +#endif + +struct _ScSecurityTokenPrivate { + SECMODModule *module; + ScSecurityTokenState state; + + CK_SLOT_ID slot_id; + gint slot_series; + + PK11SlotInfo *slot; + gchar *name; + + CERTCertificate *signing_certificate; + CERTCertificate *encryption_certificate; +}; + +static void sc_security_token_finalize (GObject *object); +static void sc_security_token_class_install_signals (ScSecurityTokenClass *token_class); +static void sc_security_token_class_install_properties (ScSecurityTokenClass *token_class); +static void sc_security_token_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void sc_security_token_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void sc_security_token_set_name (ScSecurityToken *token, const gchar *name); +static void sc_security_token_set_slot_id (ScSecurityToken *token, + gint slot_id); +static void sc_security_token_set_slot_series (ScSecurityToken *token, + gint slot_series); +static void sc_security_token_set_module (ScSecurityToken *token, + SECMODModule *module); + +static PK11SlotInfo *sc_security_token_find_slot_from_id (ScSecurityToken *token, + gint slot_id); + +static PK11SlotInfo *sc_security_token_find_slot_from_token_name (ScSecurityToken *token, + const gchar *token_name); +static gboolean sc_security_token_fetch_certificates (ScSecurityToken *token); + + +#ifndef SC_SECURITY_TOKEN_DEFAULT_SLOT_ID +#define SC_SECURITY_TOKEN_DEFAULT_SLOT_ID ((gulong) -1) +#endif + +#ifndef SC_SECURITY_TOKEN_DEFAULT_SLOT_SERIES +#define SC_SECURITY_TOKEN_DEFAULT_SLOT_SERIES -1 +#endif + +enum { + PROP_0 = 0, + PROP_NAME, + PROP_SLOT_ID, + PROP_SLOT_SERIES, + PROP_MODULE, + NUMBER_OF_PROPERTIES +}; + +enum { + INSERTED, + REMOVED, + NUMBER_OF_SIGNALS +}; + +static guint sc_security_token_signals[NUMBER_OF_SIGNALS]; + +G_DEFINE_TYPE (ScSecurityToken, sc_security_token, G_TYPE_OBJECT); + +static void +sc_security_token_class_init (ScSecurityTokenClass *token_class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (token_class); + + gobject_class->finalize = sc_security_token_finalize; + + sc_security_token_class_install_signals (token_class); + sc_security_token_class_install_properties (token_class); + + g_type_class_add_private (token_class, + sizeof (ScSecurityTokenPrivate)); +} + +static void +sc_security_token_class_install_signals (ScSecurityTokenClass *token_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (token_class); + + sc_security_token_signals[INSERTED] = + g_signal_new ("inserted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ScSecurityTokenClass, + inserted), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + token_class->inserted = NULL; + + sc_security_token_signals[REMOVED] = + g_signal_new ("removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ScSecurityTokenClass, + removed), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + token_class->removed = NULL; +} + +static void +sc_security_token_class_install_properties (ScSecurityTokenClass *token_class) +{ + GObjectClass *object_class; + GParamSpec *param_spec; + + object_class = G_OBJECT_CLASS (token_class); + object_class->set_property = sc_security_token_set_property; + object_class->get_property = sc_security_token_get_property; + + param_spec = g_param_spec_ulong ("slot-id", _("Slot ID"), + _("The slot the token is in"), + 1, G_MAXULONG, + SC_SECURITY_TOKEN_DEFAULT_SLOT_ID, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_SLOT_ID, param_spec); + + param_spec = g_param_spec_int ("slot-series", _("Slot Series"), + _("per-slot token identifier"), + -1, G_MAXINT, + SC_SECURITY_TOKEN_DEFAULT_SLOT_SERIES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_SLOT_SERIES, param_spec); + + param_spec = g_param_spec_string ("name", _("name"), + _("name"), NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_NAME, param_spec); + + param_spec = g_param_spec_pointer ("module", _("Module"), + _("security token driver"), + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_MODULE, param_spec); +} + +static void +sc_security_token_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ScSecurityToken *token = SC_SECURITY_TOKEN (object); + + switch (prop_id) + { + case PROP_NAME: + sc_security_token_set_name (token, g_value_get_string (value)); + break; + + case PROP_SLOT_ID: + sc_security_token_set_slot_id (token, + g_value_get_ulong (value)); + break; + + case PROP_SLOT_SERIES: + sc_security_token_set_slot_series (token, + g_value_get_int (value)); + break; + + case PROP_MODULE: + sc_security_token_set_module (token, + (SECMODModule *) + g_value_get_pointer (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +CK_SLOT_ID +sc_security_token_get_slot_id (ScSecurityToken *token) +{ + return token->priv->slot_id; +} + +ScSecurityTokenState +sc_security_token_get_state (ScSecurityToken *token) +{ + return token->priv->state; +} + +gchar * +sc_security_token_get_name (ScSecurityToken *token) +{ + return g_strdup (token->priv->name); +} + +gboolean +sc_security_token_is_login_token (ScSecurityToken *token) +{ + const gchar *login_token_name; + login_token_name = g_getenv ("PKCS11_LOGIN_TOKEN_NAME"); + + if ((login_token_name == NULL) || (token->priv->name == NULL)) + return FALSE; + + if (strcmp (token->priv->name, login_token_name) == 0) + return TRUE; + + return FALSE; +} + +static void +sc_security_token_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ScSecurityToken *token = SC_SECURITY_TOKEN (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_take_string (value, + sc_security_token_get_name (token)); + break; + + case PROP_SLOT_ID: + g_value_set_ulong (value, + (gulong) sc_security_token_get_slot_id (token)); + break; + + case PROP_SLOT_SERIES: + g_value_set_int (value, + sc_security_token_get_slot_series (token)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sc_security_token_set_name (ScSecurityToken *token, + const gchar *name) +{ + if (name == NULL) + return; + + if ((token->priv->name == NULL) || + (strcmp (token->priv->name, name) != 0)) { + g_free (token->priv->name); + token->priv->name = g_strdup (name); + + if (token->priv->slot == NULL) { + token->priv->slot = sc_security_token_find_slot_from_token_name (token, + token->priv->name); + + if (token->priv->slot != NULL) { + gint slot_id, slot_series; + + slot_id = PK11_GetSlotID (token->priv->slot); + if (slot_id != token->priv->slot_id) + sc_security_token_set_slot_id (token, slot_id); + + slot_series = PK11_GetSlotSeries (token->priv->slot); + if (slot_series != token->priv->slot_series) + sc_security_token_set_slot_series (token, slot_series); + + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_INSERTED); + } else { + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_REMOVED); + } + } + + + g_object_notify (G_OBJECT (token), "name"); + } +} + +static void +sc_security_token_set_slot_id (ScSecurityToken *token, + gint slot_id) +{ + if (token->priv->slot_id != slot_id) + { + token->priv->slot_id = slot_id; + + if (token->priv->slot == NULL) { + token->priv->slot = sc_security_token_find_slot_from_id (token, + token->priv->slot_id); + + if (token->priv->slot != NULL) { + const gchar *token_name; + + token_name = PK11_GetTokenName (token->priv->slot); + if ((token->priv->name == NULL) || + ((token_name != NULL) && + (strcmp (token_name, token->priv->name) != 0))) + sc_security_token_set_name (token, token_name); + + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_INSERTED); + } else { + _sc_security_token_set_state (token, SC_SECURITY_TOKEN_STATE_REMOVED); + } + } + + g_object_notify (G_OBJECT (token), "slot-id"); + } +} + +static void +sc_security_token_set_slot_series (ScSecurityToken *token, + gint slot_series) +{ + if (token->priv->slot_series != slot_series) + { + token->priv->slot_series = slot_series; + g_object_notify (G_OBJECT (token), "slot-series"); + } +} + +static void +sc_security_token_set_module (ScSecurityToken *token, + SECMODModule *module) +{ + gboolean should_notify; + + if (token->priv->module != module) + should_notify = TRUE; + else + should_notify = FALSE; + + if (token->priv->module != NULL) { + SECMOD_DestroyModule (token->priv->module); + token->priv->module = NULL; + } + + if (module != NULL) + token->priv->module = SECMOD_ReferenceModule (module); + + if (should_notify) + g_object_notify (G_OBJECT (token), "module"); +} + +gint +sc_security_token_get_slot_series (ScSecurityToken *token) +{ + return token->priv->slot_series; +} + +static void +sc_security_token_init (ScSecurityToken *token) +{ + + sc_debug ("initializing security token "); + + token->priv = G_TYPE_INSTANCE_GET_PRIVATE (token, + SC_TYPE_SECURITY_TOKEN, + ScSecurityTokenPrivate); + + if (token->priv->slot != NULL) + token->priv->name = g_strdup (PK11_GetTokenName (token->priv->slot)); +} + +static void sc_security_token_finalize (GObject *object) +{ + ScSecurityToken *token; + GObjectClass *gobject_class; + + token = SC_SECURITY_TOKEN (object); + + g_free (token->priv->name); + + sc_security_token_set_module (token, NULL); + + gobject_class = + G_OBJECT_CLASS (sc_security_token_parent_class); + + gobject_class->finalize (object); +} + +GQuark sc_security_token_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) + error_quark = g_quark_from_static_string ("sc-security-token-error-quark"); + + return error_quark; +} + +ScSecurityToken * +_sc_security_token_new (SECMODModule *module, + CK_SLOT_ID slot_id, + gint slot_series) +{ + ScSecurityToken *token; + + g_return_val_if_fail (module != NULL, NULL); + g_return_val_if_fail (slot_id >= 1, NULL); + g_return_val_if_fail (slot_series > 0, NULL); + g_return_val_if_fail (sizeof (gulong) == sizeof (slot_id), NULL); + + token = SC_SECURITY_TOKEN (g_object_new (SC_TYPE_SECURITY_TOKEN, + "module", module, + "slot-id", (gulong) slot_id, + "slot-series", slot_series, + NULL)); + return token; +} + +ScSecurityToken * +_sc_security_token_new_from_name (SECMODModule *module, + const gchar *name) +{ + ScSecurityToken *token; + + g_return_val_if_fail (module != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + + token = SC_SECURITY_TOKEN (g_object_new (SC_TYPE_SECURITY_TOKEN, + "module", module, + "name", name, + NULL)); + return token; +} + +void +_sc_security_token_set_state (ScSecurityToken *token, + ScSecurityTokenState state) +{ + /* sc_security_token_fetch_certificates (token); */ + if (token->priv->state != state) + { + token->priv->state = state; + + if (state == SC_SECURITY_TOKEN_STATE_INSERTED) { + g_signal_emit (token, sc_security_token_signals[INSERTED], 0); + } else if (state == SC_SECURITY_TOKEN_STATE_REMOVED) + g_signal_emit (token, sc_security_token_signals[REMOVED], 0); + else + g_assert_not_reached (); + } +} + +/* So we could conceivably make the closure data a pointer to the token + * or something similiar and then emit signals when we want passwords, + * but it's probably easier to just get the password up front and use + * it. So we just take the passed in g_malloc'd (well probably, who knows) + * and strdup it using NSPR's memory allocation routines. + */ +static char * +sc_security_token_password_handler (PK11SlotInfo *slot, + PRBool is_retrying, + const gchar *password) +{ + if (is_retrying) + return NULL; + + return password != NULL? PL_strdup (password): NULL; +} + +gboolean +sc_security_token_unlock (ScSecurityToken *token, + const gchar *password) +{ + SECStatus status; + + PK11_SetPasswordFunc ((PK11PasswordFunc) sc_security_token_password_handler); + + /* we pass PR_TRUE to load certificates + */ + status = PK11_Authenticate (token->priv->slot, PR_TRUE, (gpointer) password); + + if (status != SECSuccess) { + sc_debug ("could not unlock token - %d", status); + return FALSE; + } + return TRUE; +} + +static PK11SlotInfo * +sc_security_token_find_slot_from_token_name (ScSecurityToken *token, + const gchar *token_name) +{ + int i; + + for (i = 0; i < token->priv->module->slotCount; i++) { + const gchar *slot_token_name; + + slot_token_name = PK11_GetTokenName (token->priv->module->slots[i]); + + if ((slot_token_name != NULL) && + (strcmp (slot_token_name, token_name) == 0)) + return token->priv->module->slots[i]; + } + + return NULL; +} + +static PK11SlotInfo * +sc_security_token_find_slot_from_id (ScSecurityToken *token, + gint slot_id) +{ + int i; + + for (i = 0; i < token->priv->module->slotCount; i++) + if (PK11_GetSlotID (token->priv->module->slots[i]) == slot_id) + return token->priv->module->slots[i]; + + return NULL; +} + +static gboolean +sc_security_token_fetch_certificates (ScSecurityToken *token) +{ + PK11SlotInfo *slot; + CERTCertList *certificates; + CERTCertListNode *node; + SECStatus status; + int i; + + sc_security_token_unlock (token, "0000"); + + sc_debug ("fetching certificates for token in slot %lu", + token->priv->slot_id); + + slot = sc_security_token_find_slot_from_id (token, + token->priv->slot_id); + + g_assert (PK11_GetSlotID (slot) == token->priv->slot_id); + + if (i == token->priv->module->slotCount) { + sc_debug ("could not find slot %lu", token->priv->slot_id); + return FALSE; + } + + certificates = PK11_ListCertsInSlot (slot); + + sc_debug ("filtering out non-user certificates"); + if (CERT_FilterCertListForUserCerts (certificates) != SECSuccess) { + CERT_DestroyCertList (certificates); + sc_debug ("could not filter out non-user certificates"); + return FALSE; + } + + for (node = CERT_LIST_HEAD (certificates); + !CERT_LIST_END (node, certificates); + node = CERT_LIST_NEXT(node)) { + + SECCertificateUsage cert_usages; + + sc_debug ("verifying certificate for use"); + status = CERT_VerifyCertificateNow (NULL, node->cert, TRUE, + 0, NULL, &cert_usages); + + if (status != SECSuccess) { + sc_debug ("could not be verified, skipping..."); + continue; + } + + sc_debug ("got cert with usages 0x%lx", (gulong) cert_usages); + + if (token->priv->encryption_certificate == NULL) { + + sc_debug ("checking if certificate can be used for data " + "encryption"); + status = CERT_CheckCertUsage (node->cert, + KU_DATA_ENCIPHERMENT); + + if (status == SECSuccess) { + token->priv->encryption_certificate = + CERT_DupCertificate (node->cert); + } else { + sc_debug ("certificate can not be used for encryption"); + } + } + + if (token->priv->signing_certificate == NULL) { + + sc_debug ("checking if certificate can be used for data " + "signing"); + status = CERT_CheckCertUsage (node->cert, + KU_DIGITAL_SIGNATURE); + + if (status == SECSuccess) { + token->priv->signing_certificate = + CERT_DupCertificate (node->cert); + } else { + sc_debug ("certificate can not be used for signing things"); + } + } + } + return TRUE; +} + +#ifdef SC_SECURITY_TOKEN_ENABLE_TEST +#include + +static GMainLoop *event_loop; + +int +main (int argc, + char *argv[]) +{ + ScSecurityToken *token; + 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' object..."); + token = _sc_security_token_new (NULL, 1, 1); + g_message ("'security token' object created successfully"); + + g_message ("destroying previously created 'security token' object..."); + g_object_unref (token); + token = NULL; + g_message ("'security token' object destroyed successfully"); + + return 0; +} +#endif