From 6af8f7b0aad6c5af19d00fa42bdfd765db4efbe1 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 23 May 2012 13:48:49 -0400 Subject: wip: start to fold kerberos support into gsd --- configure.ac | 34 + data/Makefile.am | 1 + ...tings-daemon.plugins.identity.gschema.xml.in.in | 14 + plugins/Makefile.am | 6 + plugins/identity/Makefile.am | 144 ++ plugins/identity/gsd-alarm.c | 625 ++++++++ plugins/identity/gsd-alarm.h | 67 + plugins/identity/gsd-identity-enum-types.c.in | 42 + plugins/identity/gsd-identity-enum-types.h.in | 24 + plugins/identity/gsd-identity-inquiry-private.h | 36 + plugins/identity/gsd-identity-inquiry.c | 146 ++ plugins/identity/gsd-identity-inquiry.h | 108 ++ plugins/identity/gsd-identity-manager-private.h | 51 + plugins/identity/gsd-identity-manager.c | 260 ++++ plugins/identity/gsd-identity-manager.h | 178 +++ plugins/identity/gsd-identity-plugin.c | 798 ++++++++++ plugins/identity/gsd-identity-plugin.h | 59 + plugins/identity/gsd-identity-service.c | 674 +++++++++ plugins/identity/gsd-identity-service.h | 73 + plugins/identity/gsd-identity-test.c | 331 ++++ plugins/identity/gsd-identity-test.h | 62 + plugins/identity/gsd-identity.c | 83 + plugins/identity/gsd-identity.h | 74 + plugins/identity/gsd-kerberos-identity-inquiry.c | 363 +++++ plugins/identity/gsd-kerberos-identity-inquiry.h | 74 + plugins/identity/gsd-kerberos-identity-manager.c | 1590 ++++++++++++++++++++ plugins/identity/gsd-kerberos-identity-manager.h | 63 + plugins/identity/gsd-kerberos-identity.c | 1475 ++++++++++++++++++ plugins/identity/gsd-kerberos-identity.h | 88 ++ plugins/identity/identity.gnome-settings-plugin.in | 8 + plugins/identity/org.gnome.Identity.xml | 73 + plugins/identity/test-identity.c | 7 + 32 files changed, 7631 insertions(+) create mode 100644 data/org.gnome.settings-daemon.plugins.identity.gschema.xml.in.in create mode 100644 plugins/identity/Makefile.am create mode 100644 plugins/identity/gsd-alarm.c create mode 100644 plugins/identity/gsd-alarm.h create mode 100644 plugins/identity/gsd-identity-enum-types.c.in create mode 100644 plugins/identity/gsd-identity-enum-types.h.in create mode 100644 plugins/identity/gsd-identity-inquiry-private.h create mode 100644 plugins/identity/gsd-identity-inquiry.c create mode 100644 plugins/identity/gsd-identity-inquiry.h create mode 100644 plugins/identity/gsd-identity-manager-private.h create mode 100644 plugins/identity/gsd-identity-manager.c create mode 100644 plugins/identity/gsd-identity-manager.h create mode 100644 plugins/identity/gsd-identity-plugin.c create mode 100644 plugins/identity/gsd-identity-plugin.h create mode 100644 plugins/identity/gsd-identity-service.c create mode 100644 plugins/identity/gsd-identity-service.h create mode 100644 plugins/identity/gsd-identity-test.c create mode 100644 plugins/identity/gsd-identity-test.h create mode 100644 plugins/identity/gsd-identity.c create mode 100644 plugins/identity/gsd-identity.h create mode 100644 plugins/identity/gsd-kerberos-identity-inquiry.c create mode 100644 plugins/identity/gsd-kerberos-identity-inquiry.h create mode 100644 plugins/identity/gsd-kerberos-identity-manager.c create mode 100644 plugins/identity/gsd-kerberos-identity-manager.h create mode 100644 plugins/identity/gsd-kerberos-identity.c create mode 100644 plugins/identity/gsd-kerberos-identity.h create mode 100644 plugins/identity/identity.gnome-settings-plugin.in create mode 100644 plugins/identity/org.gnome.Identity.xml create mode 100644 plugins/identity/test-identity.c diff --git a/configure.ac b/configure.ac index 65dc0d78..419ec979 100644 --- a/configure.ac +++ b/configure.ac @@ -290,6 +290,38 @@ fi AC_SUBST(NSS_DATABASE) +dnl ============================================== +dnl kerberos section +dnl ============================================== +AC_ARG_ENABLE([kerberos], + AS_HELP_STRING([--enable-kerberos], [Use Kerberos]), + [with_kerberos=$enableval], + [with_kerberos=no]) + +AC_PATH_PROG([KRB5_CONFIG], krb5-config, none, $PATH:/usr/kerberos/bin) + +if test "x$KRB5_CONFIG" != "xnone"; then + KRB5_LIBS="`${KRB5_CONFIG} --libs krb5`" + KRB5_CFLAGS="`${KRB5_CONFIG} --cflags krb5`" + have_kerberos=yes +else + KRB5_LIBS="" + KRB5_CFLAGS="" + have_kerberos=no +fi +AC_SUBST(KRB5_CFLAGS) +AC_SUBST(KRB5_LIBS) + +if test "$with_kerberos" = "yes" ; then + if test "$have_kerberos" = "no" ; then + AC_MSG_ERROR([kerberos support requested, but not available]) + fi + + PKG_CHECK_MODULES(GCR, gcr-3) + AC_DEFINE(GCR_API_SUBJECT_TO_CHANGE, 1, [Define to use the GCR API]) + AC_DEFINE(HAVE_KERBEROS, 1, [Define to 1 if kerberos is available]) +fi +AM_CONDITIONAL(BUILD_KERBEROS, [test x$with_kerberos = xyes]) dnl ============================================== dnl power section @@ -461,6 +493,7 @@ plugins/color/Makefile plugins/common/Makefile plugins/cursor/Makefile plugins/dummy/Makefile +plugins/identity/Makefile plugins/power/Makefile plugins/housekeeping/Makefile plugins/keyboard/Makefile @@ -482,6 +515,7 @@ data/org.gnome.settings-daemon.plugins.gschema.xml.in data/org.gnome.settings-daemon.plugins.xsettings.gschema.xml.in data/org.gnome.settings-daemon.plugins.keyboard.gschema.xml.in data/org.gnome.settings-daemon.plugins.power.gschema.xml.in +data/org.gnome.settings-daemon.plugins.identity.gschema.xml.in data/org.gnome.settings-daemon.plugins.color.gschema.xml.in data/org.gnome.settings-daemon.plugins.media-keys.gschema.xml.in data/org.gnome.settings-daemon.peripherals.gschema.xml.in diff --git a/data/Makefile.am b/data/Makefile.am index 5fc9f640..1ea31ec9 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -12,6 +12,7 @@ gsettings_SCHEMAS = \ org.gnome.settings-daemon.plugins.keyboard.gschema.xml \ org.gnome.settings-daemon.plugins.power.gschema.xml \ org.gnome.settings-daemon.plugins.color.gschema.xml \ + org.gnome.settings-daemon.plugins.identity.gschema.xml \ org.gnome.settings-daemon.plugins.media-keys.gschema.xml \ org.gnome.settings-daemon.plugins.xsettings.gschema.xml \ org.gnome.settings-daemon.plugins.housekeeping.gschema.xml \ diff --git a/data/org.gnome.settings-daemon.plugins.identity.gschema.xml.in.in b/data/org.gnome.settings-daemon.plugins.identity.gschema.xml.in.in new file mode 100644 index 00000000..caa5a4cf --- /dev/null +++ b/data/org.gnome.settings-daemon.plugins.identity.gschema.xml.in.in @@ -0,0 +1,14 @@ + + + + true + <_summary>Activation of this plugin + <_description>Whether this plugin would be activated by gnome-settings-daemon or not + + + 1 + <_summary>Priority to use for this plugin + <_description>Priority to use for this plugin in gnome-settings-daemon startup queue + + + diff --git a/plugins/Makefile.am b/plugins/Makefile.am index a4aa6669..418c86ec 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -33,6 +33,12 @@ else disabled_plugins += smartcard endif +if BUILD_KERBEROS +enabled_plugins += identity +else +disabled_plugins += identity +endif + if HAVE_GUDEV enabled_plugins += orientation else diff --git a/plugins/identity/Makefile.am b/plugins/identity/Makefile.am new file mode 100644 index 00000000..d9b311c5 --- /dev/null +++ b/plugins/identity/Makefile.am @@ -0,0 +1,144 @@ +plugin_name = identity + +plugin_LTLIBRARIES = \ + libidentity.la + +dbus_built_sources = org.gnome.Identity.c org.gnome.Identity.h + +$(dbus_built_sources) : Makefile.am org.gnome.Identity.xml + gdbus-codegen \ + --interface-prefix org.gnome.Identity. \ + --c-namespace GsdIdentityService \ + --c-generate-object-manager \ + --generate-c-code org.gnome.Identity \ + org.gnome.Identity.xml \ + $(NULL) + +libidentity_headers = \ + gsd-identity-plugin.h \ + gsd-alarm.h \ + gsd-identity.h \ + gsd-identity-inquiry.h \ + gsd-identity-manager.h \ + gsd-identity-manager-private.h \ + gsd-identity-service.h \ + gsd-kerberos-identity.h \ + gsd-kerberos-identity-inquiry.h \ + gsd-kerberos-identity-manager.h + +gsd-identity-enum-types.h: gsd-identity-enum-types.h.in $(libidentity_headers) + $(AM_V_GEN) glib-mkenums --template $^ > $@ + +gsd-identity-enum-types.c: gsd-identity-enum-types.c.in $(libidentity_headers) + $(AM_V_GEN) glib-mkenums --template $^ > $@ + +libidentity_la_SOURCES = \ + $(libidentity_headers) \ + gsd-identity-plugin.c \ + gsd-alarm.c \ + gsd-identity.c \ + gsd-identity-enum-types.h \ + gsd-identity-enum-types.c \ + gsd-identity-inquiry.c \ + gsd-identity-manager.c \ + gsd-identity-service.c \ + gsd-kerberos-identity.c \ + gsd-kerberos-identity-inquiry.c \ + gsd-kerberos-identity-manager.c \ + $(dbus_built_sources) + +BUILT_SOURCES = $(dbus_built_sources) gsd-identity-enum-types.h gsd-identity-enum-types.c + +libidentity_la_CPPFLAGS = \ + -I$(top_srcdir)/gnome-settings-daemon \ + -DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DGSD_SMARTCARD_MANAGER_NSS_DB=\""$(NSS_DATABASE)"\" \ + $(AM_CPPFLAGS) + +libidentity_la_CFLAGS = \ + $(PLUGIN_CFLAGS) \ + $(SETTINGS_PLUGIN_CFLAGS) \ + $(GNOME_CFLAGS) \ + $(GCR_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(AM_CFLAGS) + +libidentity_la_LDFLAGS = \ + $(GSD_PLUGIN_LDFLAGS) + +libidentity_la_LIBADD = \ + $(SETTINGS_PLUGIN_LIBS) \ + $(GCR_LIBS) \ + $(KRB5_LIBS) + +noinst_PROGRAMS = \ + test-identity + +test_identity_SOURCES = \ + gsd-alarm.h \ + gsd-alarm.c \ + gsd-identity.h \ + gsd-identity.c \ + gsd-identity-inquiry.c \ + gsd-identity-inquiry.h \ + gsd-identity-manager.h \ + gsd-identity-manager-private.h \ + gsd-identity-manager.c \ + gsd-kerberos-identity.c \ + gsd-kerberos-identity.h \ + gsd-kerberos-identity-inquiry.c \ + gsd-kerberos-identity-inquiry.h \ + gsd-kerberos-identity-manager.h \ + gsd-kerberos-identity-manager.c \ + gsd-identity-test.c \ + test-identity.c \ + $(NULL) + +test_identity_CPPFLAGS = \ + -I$(top_srcdir)/data/ \ + -I$(top_srcdir)/gnome-settings-daemon \ + -I$(top_srcdir)/plugins/common \ + -I$(top_srcdir)/plugins/media-keys/cut-n-paste \ + -DBINDIR=\"$(bindir)\" \ + -DPIXMAPDIR=\""$(pkgdatadir)"\" \ + -DGTKBUILDERDIR=\""$(pkgdatadir)"\" \ + -DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \ + $(AM_CPPFLAGS) + +test_identity_CFLAGS = \ + $(PLUGIN_CFLAGS) \ + $(SETTINGS_PLUGIN_CFLAGS) \ + $(GNOME_CFLAGS) \ + $(GCR_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(AM_CFLAGS) + +test_identity_LDFLAGS = \ + $(GSD_PLUGIN_LDFLAGS) + +test_identity_LDADD = \ + $(top_builddir)/gnome-settings-daemon/libgsd.la \ + $(top_builddir)/plugins/common/libcommon.la \ + $(top_builddir)/plugins/media-keys/cut-n-paste/libgvc.la \ + $(SETTINGS_DAEMON_LIBS) \ + $(SETTINGS_PLUGIN_LIBS) \ + $(GCR_LIBS) \ + $(KRB5_LIBS) + +@GSD_INTLTOOL_PLUGIN_RULE@ + +plugin_in_files = \ + identity.gnome-settings-plugin.in + +plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin) + +EXTRA_DIST = \ + $(plugin_in_files) + +CLEANFILES = \ + $(plugin_DATA) + +DISTCLEANFILES = \ + $(plugin_DATA) diff --git a/plugins/identity/gsd-alarm.c b/plugins/identity/gsd-alarm.c new file mode 100644 index 00000000..b8b0b7e5 --- /dev/null +++ b/plugins/identity/gsd-alarm.c @@ -0,0 +1,625 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + * + * Author: Ray Strode + * Based on work by Colin Walters + */ + +#include "config.h" + +#include "gsd-alarm.h" + +#ifdef HAVE_TIMERFD +#include +#endif + +#include +#include + +#include +#include +#include +#include + +typedef struct { + GSource *source; + GInputStream *stream; +} Timer; + +typedef struct { + GSource *source; +} Timeout; + +#define MAX_TIMEOUT_INTERVAL (10 * 1000) + +typedef enum { + GSD_ALARM_TYPE_UNSCHEDULED, + GSD_ALARM_TYPE_TIMER, + GSD_ALARM_TYPE_TIMEOUT, +} GsdAlarmType; + +struct _GsdAlarmPrivate +{ + GCancellable *cancellable; + gulong cancelled_id; + GDateTime *time; + GDateTime *previous_wakeup_time; + GMainContext *context; + GSource *immediate_wakeup_source; + GRecMutex lock; + + GsdAlarmType type; + union { + Timer timer; + Timeout timeout; + }; +}; + +enum { + FIRED, + REARMED, + NUMBER_OF_SIGNALS, +}; + +enum { + PROP_0, + PROP_TIME +}; + +static void schedule_wakeups (GsdAlarm *self); +static void schedule_wakeups_with_timeout_source (GsdAlarm *self); +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +G_DEFINE_TYPE (GsdAlarm, gsd_alarm, G_TYPE_OBJECT); + +static void +clear_scheduled_immediate_wakeup (GsdAlarm *self) +{ + g_clear_pointer (&self->priv->immediate_wakeup_source, + (GDestroyNotify) + g_source_destroy); +} + +static void +clear_scheduled_timer_wakeups (GsdAlarm *self) +{ +#ifdef HAVE_TIMERFD + GError *error; + gboolean is_closed; + + g_clear_pointer (&self->priv->timer.source, + (GDestroyNotify) + g_source_destroy); + + error = NULL; + is_closed = g_input_stream_close (self->priv->timer.stream, + NULL, + &error); + + if (!is_closed) { + g_warning ("GsdAlarm: could not close timer stream: %s", + error->message); + g_error_free (error); + } + + g_clear_object (&self->priv->timer.stream); +#endif +} + +static void +clear_scheduled_timeout_wakeups (GsdAlarm *self) +{ + g_clear_pointer (&self->priv->timeout.source, + (GDestroyNotify) + g_source_destroy); +} + +static void +clear_scheduled_wakeups (GsdAlarm *self) +{ + g_rec_mutex_lock (&self->priv->lock); + clear_scheduled_immediate_wakeup (self); + + switch (self->priv->type) { + case GSD_ALARM_TYPE_TIMER: + clear_scheduled_timer_wakeups (self); + break; + + case GSD_ALARM_TYPE_TIMEOUT: + clear_scheduled_timeout_wakeups (self); + break; + + default: + break; + } + + g_clear_object (&self->priv->cancellable); + + g_clear_pointer (&self->priv->context, + (GDestroyNotify) + g_main_context_unref); + + g_clear_pointer (&self->priv->previous_wakeup_time, + (GDestroyNotify) + g_date_time_unref); + + g_clear_pointer (&self->priv->time, + (GDestroyNotify) + g_date_time_unref); + + g_assert (self->priv->timeout.source == NULL); + + self->priv->type = GSD_ALARM_TYPE_UNSCHEDULED; + g_rec_mutex_unlock (&self->priv->lock); +} + +static void +gsd_alarm_finalize (GObject *object) +{ + GsdAlarm *self = GSD_ALARM (object); + + clear_scheduled_wakeups (self); + + G_OBJECT_CLASS (gsd_alarm_parent_class)->finalize (object); +} + +static void +gsd_alarm_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *param_spec) +{ + GsdAlarm *self = GSD_ALARM (object); + GDateTime *time; + + switch (property_id) { + case PROP_TIME: + time = (GDateTime *) g_value_get_boxed (value); + gsd_alarm_set_time (self, time, self->priv->cancellable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + property_id, + param_spec); + break; + } +} + +static void +gsd_alarm_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec) +{ + GsdAlarm *self = GSD_ALARM (object); + + switch (property_id) { + case PROP_TIME: + g_value_set_boxed (value, self->priv->time); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + property_id, + param_spec); + break; + } +} + +static void +gsd_alarm_class_init (GsdAlarmClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_alarm_finalize; + object_class->get_property = gsd_alarm_get_property; + object_class->set_property = gsd_alarm_set_property; + + g_type_class_add_private (klass, sizeof (GsdAlarmPrivate)); + + signals[FIRED] = g_signal_new ("fired", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[REARMED] = g_signal_new ("rearmed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + g_object_class_install_property (object_class, + PROP_TIME, + g_param_spec_boxed ("time", + _("Time"), + _("Time to fire"), + G_TYPE_DATE_TIME, + G_PARAM_READWRITE)); +} + +static void +gsd_alarm_init (GsdAlarm *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_ALARM, + GsdAlarmPrivate); + self->priv->type = GSD_ALARM_TYPE_UNSCHEDULED; + g_rec_mutex_init (&self->priv->lock); +} + +static void +on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + GsdAlarm *self = GSD_ALARM (user_data); + + clear_scheduled_wakeups (self); +} + +static void +fire_alarm (GsdAlarm *self) +{ + g_signal_emit (G_OBJECT (self), signals[FIRED], 0); +} + +static void +rearm_alarm (GsdAlarm *self) +{ + g_signal_emit (G_OBJECT (self), signals[REARMED], 0); +} + +static void +fire_or_rearm_alarm (GsdAlarm *self) +{ + GTimeSpan time_until_fire; + GTimeSpan previous_time_until_fire; + GDateTime *now; + + now = g_date_time_new_now_local (); + time_until_fire = g_date_time_difference (self->priv->time, now); + + if (self->priv->previous_wakeup_time == NULL) { + self->priv->previous_wakeup_time = now; + + /* If, according to the time, we're past when we should have fired, + * then fire the alarm. + */ + if (time_until_fire <= 0) { + fire_alarm (self); + } + } else { + previous_time_until_fire = g_date_time_difference (self->priv->time, + self->priv->previous_wakeup_time); + + g_date_time_unref (self->priv->previous_wakeup_time); + self->priv->previous_wakeup_time = now; + + /* If, according to the time, we're past when we should have fired, + * and this is the first wakeup where that's been true then fire + * the alarm. The first check makes sure we don't fire prematurely, + * and the second check makes sure we don't fire more than once + */ + if (time_until_fire <= 0 && previous_time_until_fire > 0) { + fire_alarm (self); + + /* If, according to the time, we're before when we should fire, + * and we previously fired the alarm, then we've jumped back in + * time and need to rearm the alarm. + */ + } else if (time_until_fire > 0 && previous_time_until_fire <= 0) { + rearm_alarm (self); + } + } +} + +static gboolean +on_immediate_wakeup_source_ready (GsdAlarm *self) +{ + g_return_val_if_fail (self->priv->type != GSD_ALARM_TYPE_UNSCHEDULED, FALSE); + + g_rec_mutex_lock (&self->priv->lock); + if (g_cancellable_is_cancelled (self->priv->cancellable)) { + goto out; + } + + fire_or_rearm_alarm (self); + +out: + g_rec_mutex_unlock (&self->priv->lock); + return FALSE; +} + +#ifdef HAVE_TIMERFD +static gboolean +on_timer_source_ready (GObject *stream, + GsdAlarm *self) +{ + gint64 number_of_fires; + gssize bytes_read; + gboolean run_again = FALSE; + + g_return_val_if_fail (GSD_IS_ALARM (self), FALSE); + g_return_val_if_fail (self->priv->type == GSD_ALARM_TYPE_TIMER, FALSE); + + g_rec_mutex_lock (&self->priv->lock); + if (g_cancellable_is_cancelled (self->priv->cancellable)) { + goto out; + } + + bytes_read = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream), + &number_of_fires, + sizeof (gint64), + NULL, + NULL); + + if (bytes_read == sizeof (gint64)) { + if (number_of_fires < 0 || number_of_fires > 1) { + g_warning ("GsdAlarm: expected timerfd to report firing once," + "but it reported firing %ld times\n", + (long) number_of_fires); + } + } + + fire_or_rearm_alarm (self); + run_again = TRUE; +out: + g_rec_mutex_unlock (&self->priv->lock); + return run_again; +} + +static void +clear_timer_source_pointer (GsdAlarm *self) +{ + self->priv->timer.source = NULL; +} +#endif + +static gboolean +schedule_wakeups_with_timerfd (GsdAlarm *self) +{ +#ifdef HAVE_TIMERFD + struct itimerspec timer_spec; + int fd; + int result; + static gboolean seen_before = FALSE; + + if (!seen_before) { + g_debug ("GsdAlarm: trying to use kernel timer"); + seen_before = TRUE; + } + + fd = timerfd_create (CLOCK_REALTIME, TFD_CLOEXEC | TFD_NONBLOCK); + + if (fd < 0) { + g_debug ("GsdAlarm: could not create timer fd: %m"); + return FALSE; + } + + memset (&timer_spec, 0, sizeof (timer_spec)); + timer_spec.it_value.tv_sec = g_date_time_to_unix (self->priv->time) + 1; + + result = timerfd_settime (fd, + TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, + &timer_spec, + NULL); + + if (result < 0) { + g_debug ("GsdAlarm: could not set timer: %m"); + return FALSE; + } + + self->priv->type = GSD_ALARM_TYPE_TIMER; + self->priv->timer.stream = g_unix_input_stream_new (fd, TRUE); + + self->priv->timer.source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (self->priv->timer.stream), + self->priv->cancellable); + g_source_set_callback (self->priv->timer.source, + (GSourceFunc) + on_timer_source_ready, + self, + (GDestroyNotify) + clear_timer_source_pointer); + g_source_attach (self->priv->timer.source, + self->priv->context); + g_source_unref (self->priv->timer.source); + + return TRUE; + +#endif /* HAVE_TIMERFD */ + + return FALSE; +} + +static gboolean +on_timeout_source_ready (GsdAlarm *self) +{ + g_return_val_if_fail (GSD_IS_ALARM (self), FALSE); + + g_rec_mutex_lock (&self->priv->lock); + + if (g_cancellable_is_cancelled (self->priv->cancellable) || + self->priv->type == GSD_ALARM_TYPE_UNSCHEDULED) { + goto out; + } + + fire_or_rearm_alarm (self); + + if (g_cancellable_is_cancelled (self->priv->cancellable)) { + goto out; + } + + schedule_wakeups_with_timeout_source (self); + +out: + g_rec_mutex_unlock (&self->priv->lock); + return FALSE; +} + +static void +clear_timeout_source_pointer (GsdAlarm *self) +{ + self->priv->timeout.source = NULL; +} + +static void +schedule_wakeups_with_timeout_source (GsdAlarm *self) +{ + GDateTime *now; + GTimeSpan time_span; + guint interval; + + self->priv->type = GSD_ALARM_TYPE_TIMEOUT; + + now = g_date_time_new_now_local (); + time_span = g_date_time_difference (self->priv->time, now); + g_date_time_unref (now); + + time_span = CLAMP (time_span, 1000 * G_TIME_SPAN_MILLISECOND, G_MAXUINT * G_TIME_SPAN_MILLISECOND); + interval = (guint) time_span / G_TIME_SPAN_MILLISECOND; + + /* We poll every 10 seconds or so because we want to catch time skew + */ + interval = MIN (interval, MAX_TIMEOUT_INTERVAL); + + self->priv->timeout.source = g_timeout_source_new (interval); + g_source_set_callback (self->priv->timeout.source, + (GSourceFunc) + on_timeout_source_ready, + self, + (GDestroyNotify) + clear_timeout_source_pointer); + + g_source_attach (self->priv->timeout.source, + self->priv->context); + g_source_unref (self->priv->timeout.source); +} + +static void +schedule_wakeups (GsdAlarm *self) +{ + gboolean wakeup_scheduled; + + wakeup_scheduled = schedule_wakeups_with_timerfd (self); + + if (!wakeup_scheduled) { + static gboolean seen_before = FALSE; + + if (!seen_before) { + g_debug ("GsdAlarm: falling back to polling timeout"); + seen_before = TRUE; + } + schedule_wakeups_with_timeout_source (self); + } +} + +static void +clear_immediate_wakeup_source_pointer (GsdAlarm *self) +{ + self->priv->immediate_wakeup_source = NULL; +} + +static void +schedule_immediate_wakeup (GsdAlarm *self) +{ + self->priv->immediate_wakeup_source = g_idle_source_new (); + + g_source_set_callback (self->priv->immediate_wakeup_source, + (GSourceFunc) + on_immediate_wakeup_source_ready, + self, + (GDestroyNotify) + clear_immediate_wakeup_source_pointer); + g_source_attach (self->priv->immediate_wakeup_source, + self->priv->context); + g_source_unref (self->priv->immediate_wakeup_source); +} + +void +gsd_alarm_set_time (GsdAlarm *self, + GDateTime *time, + GCancellable *cancellable) +{ + if (g_cancellable_is_cancelled (cancellable)) { + return; + } + + if (self->priv->cancellable != NULL && + self->priv->cancellable != cancellable) { + g_cancellable_cancel (self->priv->cancellable); + } + + if (cancellable != NULL) { + g_object_ref (cancellable); + } + + if (self->priv->cancelled_id != 0) { + g_cancellable_disconnect (self->priv->cancellable, + self->priv->cancelled_id); + } + + g_clear_object (&self->priv->cancellable); + + if (cancellable != NULL) { + self->priv->cancellable = cancellable; + } else { + self->priv->cancellable = g_cancellable_new (); + } + + self->priv->cancelled_id = g_cancellable_connect (self->priv->cancellable, + G_CALLBACK (on_cancelled), + self, + NULL); + + g_date_time_ref (time); + + if (self->priv->time != NULL) { + g_date_time_unref (self->priv->time); + } + + self->priv->time = time; + + self->priv->context = g_main_context_ref (g_main_context_default ()); + + g_object_notify (G_OBJECT (self), "time"); + + schedule_wakeups (self); + + /* Wake up right away, in case it's already expired leaving the gate */ + schedule_immediate_wakeup (self); +} + +GDateTime * +gsd_alarm_get_time (GsdAlarm *self) +{ + return self->priv->time; +} + +GsdAlarm * +gsd_alarm_new (void) +{ + GsdAlarm *self; + + self = GSD_ALARM (g_object_new (GSD_TYPE_ALARM, NULL)); + + return GSD_ALARM (self); +} diff --git a/plugins/identity/gsd-alarm.h b/plugins/identity/gsd-alarm.h new file mode 100644 index 00000000..da17d4ca --- /dev/null +++ b/plugins/identity/gsd-alarm.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_ALARM_H__ +#define __GSD_ALARM_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_ALARM (gsd_alarm_get_type ()) +#define GSD_ALARM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_ALARM, GsdAlarm)) +#define GSD_ALARM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_ALARM, GsdAlarmClass)) +#define GSD_IS_ALARM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_ALARM)) +#define GSD_IS_ALARM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSD_TYPE_ALARM)) +#define GSD_ALARM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSD_TYPE_ALARM, GsdAlarmClass)) + +typedef struct _GsdAlarm GsdAlarm; +typedef struct _GsdAlarmClass GsdAlarmClass; +typedef struct _GsdAlarmPrivate GsdAlarmPrivate; + +struct _GsdAlarm +{ + GObject parent; + + GsdAlarmPrivate *priv; +}; + +struct _GsdAlarmClass +{ + GObjectClass parent_class; + + void (* fired) (GsdAlarm *alarm); + void (* rearmed) (GsdAlarm *alarm); +}; + +GType gsd_alarm_get_type (void); + +GsdAlarm *gsd_alarm_new (void); +void gsd_alarm_set_time (GsdAlarm *alarm, + GDateTime *time, + GCancellable *cancellable); +GDateTime *gsd_alarm_get_time (GsdAlarm *alarm); +G_END_DECLS + +#endif /* __GSD_ALARM_H__ */ diff --git a/plugins/identity/gsd-identity-enum-types.c.in b/plugins/identity/gsd-identity-enum-types.c.in new file mode 100644 index 00000000..c0286907 --- /dev/null +++ b/plugins/identity/gsd-identity-enum-types.c.in @@ -0,0 +1,42 @@ +/*** BEGIN file-header ***/ + +#include + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +#include "@filename@" +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; + +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + /**/ +/*** END file-tail ***/ diff --git a/plugins/identity/gsd-identity-enum-types.h.in b/plugins/identity/gsd-identity-enum-types.h.in new file mode 100644 index 00000000..79dcc3d8 --- /dev/null +++ b/plugins/identity/gsd-identity-enum-types.h.in @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef GSD_IDENTITY_ENUM_TYPES_H +#define GSD_IDENTITY_ENUM_TYPES_H + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* GSD_IDENTITY_ENUM_TYPES_H */ +/*** END file-tail ***/ diff --git a/plugins/identity/gsd-identity-inquiry-private.h b/plugins/identity/gsd-identity-inquiry-private.h new file mode 100644 index 00000000..e4df1bf1 --- /dev/null +++ b/plugins/identity/gsd-identity-inquiry-private.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_INQUIRY_PRIVATE_H__ +#define __GSD_IDENTITY_INQUIRY_PRIVATE_H__ + +#include +#include + +#include "gsd-identity-inquiry.h" + +G_BEGIN_DECLS + +void _gsd_identity_inquiry_emit_complete (GsdIdentityInquiry *inquiry); +G_END_DECLS + +#endif /* __GSD_IDENTITY_INQUIRY_PRIVATE_H__ */ diff --git a/plugins/identity/gsd-identity-inquiry.c b/plugins/identity/gsd-identity-inquiry.c new file mode 100644 index 00000000..fa1646c0 --- /dev/null +++ b/plugins/identity/gsd-identity-inquiry.c @@ -0,0 +1,146 @@ +/* -*- Mode: C; tab-width: 8; ident-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + * + * Author: Ray Strode + */ + +#include "config.h" + +#include "gsd-identity-inquiry.h" +#include "gsd-identity-inquiry-private.h" + +#include +#include +#include + +enum { + COMPLETE, + NUMBER_OF_SIGNALS, +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +G_DEFINE_INTERFACE (GsdIdentityInquiry, + gsd_identity_inquiry, + G_TYPE_OBJECT); + +static void +gsd_identity_inquiry_default_init (GsdIdentityInquiryInterface *interface) +{ + signals[COMPLETE] = g_signal_new ("complete", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +void +_gsd_identity_inquiry_emit_complete (GsdIdentityInquiry *self) +{ + g_signal_emit (G_OBJECT (self), signals[COMPLETE], 0); +} + +char * +gsd_identity_inquiry_get_name (GsdIdentityInquiry *self) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), NULL); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->get_name (self); +} + +char * +gsd_identity_inquiry_get_banner (GsdIdentityInquiry *self) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), NULL); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->get_banner (self); +} + +gboolean +gsd_identity_inquiry_is_complete (GsdIdentityInquiry *self) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), TRUE); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->is_complete (self); +} + +void +gsd_identity_inquiry_iter_init (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry) +{ + g_return_if_fail (GSD_IS_IDENTITY_INQUIRY (inquiry)); + + GSD_IDENTITY_INQUIRY_GET_IFACE (inquiry)->iter_init (iter, inquiry); +} + +GsdIdentityQuery * +gsd_identity_inquiry_iter_next (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (inquiry), NULL); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (inquiry)->iter_next (iter, inquiry); +} + +GsdIdentity * +gsd_identity_inquiry_get_identity (GsdIdentityInquiry *self) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), NULL); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->get_identity (self); +} + +GsdIdentityQueryMode +gsd_identity_query_get_mode (GsdIdentityInquiry *self, + GsdIdentityQuery *query) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), + GSD_IDENTITY_QUERY_MODE_INVISIBLE); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->get_mode (self, query); +} + +char * +gsd_identity_query_get_prompt (GsdIdentityInquiry *self, + GsdIdentityQuery *query) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), NULL); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->get_prompt (self, query); +} + +void +gsd_identity_inquiry_answer_query (GsdIdentityInquiry *self, + GsdIdentityQuery *query, + const char *answer) +{ + g_return_if_fail (GSD_IS_IDENTITY_INQUIRY (self)); + + GSD_IDENTITY_INQUIRY_GET_IFACE (self)->answer_query (self, query, answer); +} + +gboolean +gsd_identity_query_is_answered (GsdIdentityInquiry *self, + GsdIdentityQuery *query) +{ + g_return_val_if_fail (GSD_IS_IDENTITY_INQUIRY (self), FALSE); + + return GSD_IDENTITY_INQUIRY_GET_IFACE (self)->is_answered (self, query); +} diff --git a/plugins/identity/gsd-identity-inquiry.h b/plugins/identity/gsd-identity-inquiry.h new file mode 100644 index 00000000..771ec10c --- /dev/null +++ b/plugins/identity/gsd-identity-inquiry.h @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 8; ident-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_INQUIRY_H__ +#define __GSD_IDENTITY_INQUIRY_H__ + +#include + +#include +#include +#include + +#include "gsd-identity.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY_INQUIRY (gsd_identity_inquiry_get_type ()) +#define GSD_IDENTITY_INQUIRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_IDENTITY_INQUIRY, GsdIdentityInquiry)) +#define GSD_IDENTITY_INQUIRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_IDENTITY_INQUIRY, GsdIdentityInquiryClass)) +#define GSD_IS_IDENTITY_INQUIRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_IDENTITY_INQUIRY)) +#define GSD_IDENTITY_INQUIRY_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GSD_TYPE_IDENTITY_INQUIRY, GsdIdentityInquiryInterface)) +typedef struct _GsdIdentityInquiry GsdIdentityInquiry; +typedef struct _GsdIdentityInquiryInterface GsdIdentityInquiryInterface; +typedef struct _GsdIdentityInquiryIter GsdIdentityInquiryIter; + +typedef struct _GsdIdentityQuery GsdIdentityQuery; + +typedef void (* GsdIdentityInquiryFunc) (GsdIdentityInquiry *inquiry, + GCancellable *cancellable, + gpointer user_data); + +typedef enum +{ + GSD_IDENTITY_QUERY_MODE_INVISIBLE, + GSD_IDENTITY_QUERY_MODE_VISIBLE +} GsdIdentityQueryMode; + +struct _GsdIdentityInquiryIter +{ + gpointer data; +}; + +struct _GsdIdentityInquiryInterface +{ + GTypeInterface base_interface; + + GsdIdentity * (* get_identity) (GsdIdentityInquiry *inquiry); + char * (* get_name) (GsdIdentityInquiry *inquiry); + char * (* get_banner) (GsdIdentityInquiry *inquiry); + gboolean (* is_complete) (GsdIdentityInquiry *inquiry); + void (* answer_query) (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query, + const char *answer); + + void (* iter_init) (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry); + GsdIdentityQuery * (* iter_next) (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry); + + GsdIdentityQueryMode (* get_mode) (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); + char * (* get_prompt) (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); + gboolean (* is_answered) (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); +}; + +GType gsd_identity_inquiry_get_type (void); + +GsdIdentity *gsd_identity_inquiry_get_identity (GsdIdentityInquiry *inquiry); +char *gsd_identity_inquiry_get_name (GsdIdentityInquiry *inquiry); +char *gsd_identity_inquiry_get_banner (GsdIdentityInquiry *inquiry); +gboolean gsd_identity_inquiry_is_complete (GsdIdentityInquiry *inquiry); +void gsd_identity_inquiry_answer_query (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query, + const char *answer); + +void gsd_identity_inquiry_iter_init (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry); +GsdIdentityQuery *gsd_identity_inquiry_iter_next (GsdIdentityInquiryIter *iter, GsdIdentityInquiry *inquiry); + +GsdIdentityQueryMode gsd_identity_query_get_mode (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); +char *gsd_identity_query_get_prompt (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); +gboolean gsd_identity_query_is_answered (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query); + +#endif /* __GSD_IDENTITY_INQUIRY_H__ */ diff --git a/plugins/identity/gsd-identity-manager-private.h b/plugins/identity/gsd-identity-manager-private.h new file mode 100644 index 00000000..13b85127 --- /dev/null +++ b/plugins/identity/gsd-identity-manager-private.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_MANAGER_PRIVATE_H__ +#define __GSD_IDENTITY_MANAGER_PRIVATE_H__ + +#include +#include + +#include "gsd-identity-manager.h" + +G_BEGIN_DECLS + +void _gsd_identity_manager_emit_identity_added (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +void _gsd_identity_manager_emit_identity_removed (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +void _gsd_identity_manager_emit_identity_refreshed (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +void _gsd_identity_manager_emit_identity_renamed (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + +void _gsd_identity_manager_emit_identity_expiring (GsdIdentityManager *self, + GsdIdentity *identity); + +void _gsd_identity_manager_emit_identity_needs_renewal (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +void _gsd_identity_manager_emit_identity_expired (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +G_END_DECLS + +#endif /* __GSD_IDENTITY_MANAGER_PRIVATE_H__ */ diff --git a/plugins/identity/gsd-identity-manager.c b/plugins/identity/gsd-identity-manager.c new file mode 100644 index 00000000..be6c6a9c --- /dev/null +++ b/plugins/identity/gsd-identity-manager.c @@ -0,0 +1,260 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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 "config.h" + +#include +#include +#include + +#include "gsd-identity-manager.h" +#include "gsd-identity-manager-private.h" + +enum { + IDENTITY_ADDED, + IDENTITY_REMOVED, + IDENTITY_RENAMED, + IDENTITY_REFRESHED, + IDENTITY_NEEDS_RENEWAL, + IDENTITY_EXPIRING, + IDENTITY_EXPIRED, + NUMBER_OF_SIGNALS, +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +G_DEFINE_INTERFACE (GsdIdentityManager, gsd_identity_manager, G_TYPE_OBJECT); + +static void +gsd_identity_manager_default_init (GsdIdentityManagerInterface *interface) +{ + signals[IDENTITY_ADDED] = g_signal_new ("identity-added", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_added), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_REMOVED] = g_signal_new ("identity-removed", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_removed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_REFRESHED] = g_signal_new ("identity-refreshed", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_refreshed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_RENAMED] = g_signal_new ("identity-renamed", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_renamed), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_NEEDS_RENEWAL] = g_signal_new ("identity-needs-renewal", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_needs_renewal), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_EXPIRING] = g_signal_new ("identity-expiring", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_expiring), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); + signals[IDENTITY_EXPIRED] = g_signal_new ("identity-expired", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityManagerInterface, identity_expired), + NULL, NULL, NULL, + G_TYPE_NONE, 1, GSD_TYPE_IDENTITY); +} + +GQuark +gsd_identity_manager_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gsd-identity-manager-error"); + } + + return error_quark; +} + +void +gsd_identity_manager_get_identity (GsdIdentityManager *self, + const char *identifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->get_identity (self, + identifier, + cancellable, + callback, + user_data); +} + +GsdIdentity * +gsd_identity_manager_get_identity_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + return GSD_IDENTITY_MANAGER_GET_IFACE (self)->get_identity_finish (self, result, error); +} + +void +gsd_identity_manager_list_identities (GsdIdentityManager *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->list_identities (self, + cancellable, + callback, + user_data); +} + +GList * +gsd_identity_manager_list_identities_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + return GSD_IDENTITY_MANAGER_GET_IFACE (self)->list_identities_finish (self, + result, + error); +} + +void +gsd_identity_manager_renew_identity (GsdIdentityManager *self, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->renew_identity (self, identity, cancellable, callback, user_data); +} + +void +gsd_identity_manager_renew_identity_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->renew_identity_finish (self, result, error); +} + +void +gsd_identity_manager_sign_identity_in (GsdIdentityManager *self, + const char *identifier, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->sign_identity_in (self, identifier, inquiry_func, inquiry_data, cancellable, callback, user_data); +} + +GsdIdentity * +gsd_identity_manager_sign_identity_in_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + return GSD_IDENTITY_MANAGER_GET_IFACE (self)->sign_identity_in_finish (self, result, error); +} + +void +gsd_identity_manager_sign_identity_out (GsdIdentityManager *self, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->sign_identity_out (self, identity, cancellable, callback, user_data); +} + +void +gsd_identity_manager_sign_identity_out_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + GSD_IDENTITY_MANAGER_GET_IFACE (self)->sign_identity_out_finish (self, result, error); +} + +char * +gsd_identity_manager_name_identity (GsdIdentityManager *self, + GsdIdentity *identity) +{ + return GSD_IDENTITY_MANAGER_GET_IFACE (self)->name_identity (self, + identity); +} + +void +_gsd_identity_manager_emit_identity_added (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_ADDED], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_removed (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_REMOVED], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_renamed (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_RENAMED], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_refreshed (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_REFRESHED], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_needs_renewal (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_NEEDS_RENEWAL], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_expiring (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_EXPIRING], 0, identity); +} + +void +_gsd_identity_manager_emit_identity_expired (GsdIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_emit (G_OBJECT (self), signals[IDENTITY_EXPIRED], 0, identity); +} + diff --git a/plugins/identity/gsd-identity-manager.h b/plugins/identity/gsd-identity-manager.h new file mode 100644 index 00000000..56677d93 --- /dev/null +++ b/plugins/identity/gsd-identity-manager.h @@ -0,0 +1,178 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_MANAGER_H__ +#define __GSD_IDENTITY_MANAGER_H__ + +#include +#include +#include + +#include "gsd-identity.h" +#include "gsd-identity-inquiry.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY_MANAGER (gsd_identity_manager_get_type ()) +#define GSD_IDENTITY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_IDENTITY_MANAGER, GsdIdentityManager)) +#define GSD_IDENTITY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_IDENTITY_MANAGER, GsdIdentityManagerInterface)) +#define GSD_IS_IDENTITY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_IDENTITY_MANAGER)) +#define GSD_IDENTITY_MANAGER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GSD_TYPE_IDENTITY_MANAGER, GsdIdentityManagerInterface)) +#define GSD_IDENTITY_MANAGER_ERROR (gsd_identity_manager_error_quark ()) + +typedef struct _GsdIdentityManager GsdIdentityManager; +typedef struct _GsdIdentityManagerInterface GsdIdentityManagerInterface; + +struct _GsdIdentityManagerInterface +{ + GTypeInterface base_interface; + + /* Signals */ + void (* identity_added) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + + void (* identity_removed) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + void (* identity_renamed) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + void (* identity_refreshed) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + void (* identity_needs_renewal) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + void (* identity_expiring) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + void (* identity_expired) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + + /* Virtual Functions */ + void (* get_identity) (GsdIdentityManager *identity_manager, + const char *identifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GsdIdentity * (* get_identity_finish) (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + void (* list_identities) (GsdIdentityManager *identity_manager, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GList * (* list_identities_finish) (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + + void (* sign_identity_in) (GsdIdentityManager *identity_manager, + const char *identifier, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GsdIdentity * (* sign_identity_in_finish) (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + + void (* sign_identity_out) (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + void (* sign_identity_out_finish) (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + + void (* renew_identity) (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + void (* renew_identity_finish) (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + + char * (* name_identity) (GsdIdentityManager *identity_manager, + GsdIdentity *identity); +}; + +typedef enum +{ + GSD_IDENTITY_MANAGER_ERROR_INITIALIZING, + GSD_IDENTITY_MANAGER_ERROR_IDENTITY_NOT_FOUND, + GSD_IDENTITY_MANAGER_ERROR_CREATING_IDENTITY, + GSD_IDENTITY_MANAGER_ERROR_ACCESSING_CREDENTIALS, + GSD_IDENTITY_MANAGER_ERROR_UNSUPPORTED_CREDENTIALS +} GsdIdentityManagerError; + +GType gsd_identity_manager_get_type (void); +GQuark gsd_identity_manager_error_quark (void); + +void gsd_identity_manager_get_identity (GsdIdentityManager *identity_manager, + const char *identifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GsdIdentity *gsd_identity_manager_get_identity_finish (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); +void gsd_identity_manager_list_identities (GsdIdentityManager *identity_manager, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GList * gsd_identity_manager_list_identities_finish (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + +void gsd_identity_manager_sign_identity_in (GsdIdentityManager *identity_manager, + const char *identifier, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GsdIdentity * gsd_identity_manager_sign_identity_in_finish (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + +void gsd_identity_manager_sign_identity_out (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void gsd_identity_manager_sign_identity_out_finish (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + +void gsd_identity_manager_renew_identity (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void gsd_identity_manager_renew_identity_finish (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GError **error); + +char *gsd_identity_manager_name_identity (GsdIdentityManager *identity_manager, + GsdIdentity *identity); + +G_END_DECLS + +#endif /* __GSD_IDENTITY_MANAGER_H__ */ diff --git a/plugins/identity/gsd-identity-plugin.c b/plugins/identity/gsd-identity-plugin.c new file mode 100644 index 00000000..ce457252 --- /dev/null +++ b/plugins/identity/gsd-identity-plugin.c @@ -0,0 +1,798 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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 "config.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include "gnome-settings-plugin.h" +#include "gsd-identity-plugin.h" + +#include "gsd-kerberos-identity-manager.h" +#include "gsd-identity-service.h" + +struct GsdIdentityPluginPrivate { + GsdIdentityManager *identity_manager; + GsdIdentityService *identity_service; + guint bus_id; +}; + +#define GSD_IDENTITY_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GSD_TYPE_IDENTITY_PLUGIN, GsdIdentityPluginPrivate)) + +GNOME_SETTINGS_PLUGIN_REGISTER (GsdIdentityPlugin, gsd_identity_plugin); + +static void +gsd_identity_plugin_init (GsdIdentityPlugin *self) +{ + self->priv = GSD_IDENTITY_PLUGIN_GET_PRIVATE (self); + + g_debug ("GsdIdentityPlugin initializing"); +} + +static void +gsd_identity_plugin_finalize (GObject *object) +{ + GsdIdentityPlugin *self; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_IDENTITY_PLUGIN (object)); + + g_debug ("GsdIdentityPlugin: finalizing"); + + self = GSD_IDENTITY_PLUGIN (object); + + g_return_if_fail (self->priv != NULL); + + g_clear_object (&self->priv->identity_manager); + + G_OBJECT_CLASS (gsd_identity_plugin_parent_class)->finalize (object); +} + +static void +on_identity_renewed (GsdIdentityManager *manager, + GAsyncResult *result, + GnomeSettingsPlugin *self) +{ + GError *error; + + error = NULL; + gsd_identity_manager_renew_identity_finish (manager, + result, + &error); + + if (error != NULL) { + g_debug ("GsdIdentityPlugin: could not renew identity: %s", + error->message); + g_error_free (error); + return; + } + + g_debug ("GsdIdentityPlugin: identity renewed"); +} + +static void +on_identity_needs_renewal (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityPlugin *self) +{ + g_debug ("GsdIdentityPlugin: identity needs renewal"); + gsd_identity_manager_renew_identity (GSD_IDENTITY_MANAGER (self->priv->identity_manager), + identity, + NULL, + (GAsyncReadyCallback) + on_identity_renewed, + self); +} + +static void +on_identity_signed_in (GsdIdentityManager *manager, + GAsyncResult *result, + GnomeSettingsPlugin *self) +{ + GError *error; + + error = NULL; + gsd_identity_manager_sign_identity_in_finish (manager, + result, + &error); + + if (error != NULL) { + g_debug ("GsdIdentityPlugin: could not sign in identity: %s", + error->message); + g_error_free (error); + return; + } + + g_debug ("GsdIdentityPlugin: identity signed in"); +} + +typedef struct { + GsdIdentityPlugin *plugin; + GsdIdentity *identity; + NotifyNotification *notification; + GCancellable *cancellable; + gulong refreshed_signal_id; +} SignInRequest; + +static SignInRequest * +sign_in_request_new (GsdIdentityPlugin *plugin, + GsdIdentity *identity, + NotifyNotification *notification, + GCancellable *cancellable) +{ + SignInRequest *request; + + request = g_slice_new0 (SignInRequest); + + request->plugin = plugin; + request->identity = g_object_ref (identity); + request->notification = notification; + request->cancellable = g_object_ref (cancellable); + + return request; +} + +static void +sign_in_request_free (SignInRequest *data) +{ + GsdIdentityPlugin *plugin = data->plugin; + + g_signal_handler_disconnect (plugin->priv->identity_manager, + data->refreshed_signal_id); + g_object_set_data (G_OBJECT (data->identity), "sign-in-request", NULL); + g_clear_object (&data->identity); + g_clear_object (&data->cancellable); + g_slice_free (SignInRequest, data); +} + +typedef struct { + GsdIdentityPlugin *plugin; + GsdIdentity *identity; + GsdIdentityInquiry *inquiry; + GsdIdentityQuery *query; + GcrSystemPrompt *prompt; + GCancellable *cancellable; +} SystemPromptRequest; + +static SystemPromptRequest * +system_prompt_request_new (GsdIdentityPlugin *plugin, + GcrSystemPrompt *prompt, + GsdIdentity *identity, + GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query, + GCancellable *cancellable) +{ + SystemPromptRequest *data; + + data = g_slice_new0 (SystemPromptRequest); + + data->plugin = plugin; + data->prompt = prompt; + data->identity = g_object_ref (identity); + data->inquiry = g_object_ref (inquiry); + data->query = query; + data->cancellable = g_object_ref (cancellable); + + return data; +} + +static void +system_prompt_request_free (SystemPromptRequest *data) +{ + g_clear_object (&data->identity); + g_clear_object (&data->inquiry); + g_clear_object (&data->cancellable); + g_slice_free (SystemPromptRequest, data); +} + +static void +close_system_prompt (GsdIdentityManager *manager, + GsdIdentity *identity, + SystemPromptRequest *data) +{ + GError *error; + + /* Only close the prompt if the identity we're + * waiting on got refreshed + */ + if (data->identity != identity) { + return; + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (manager), + G_CALLBACK (close_system_prompt), + data); + error = NULL; + if (!gcr_system_prompt_close (data->prompt, + NULL, + &error)) { + if (error != NULL) { + g_debug ("GsdIdentityPlugin: could not close system prompt: %s", + error->message); + g_error_free (error); + } + } +} + +static void +on_password_system_prompt_answered (GcrPrompt *prompt, + GAsyncResult *result, + SystemPromptRequest *request) +{ + GsdIdentityPlugin *self = request->plugin; + GsdIdentityInquiry *inquiry = request->inquiry; + GsdIdentity *identity = request->identity; + GsdIdentityQuery *query = request->query; + GCancellable *cancellable = request->cancellable; + GError *error; + const char *password; + + error = NULL; + password = gcr_prompt_password_finish (prompt, result, &error); + + if (password == NULL) { + if (error != NULL) { + g_debug ("GsdIdentityPlugin: could not get password from user: %s", + error->message); + g_error_free (error); + } else { + g_cancellable_cancel (cancellable); + } + } else if (!g_cancellable_is_cancelled (cancellable)) { + gsd_identity_inquiry_answer_query (inquiry, + query, + password); + } + + close_system_prompt (self->priv->identity_manager, identity, request); + system_prompt_request_free (request); +} + +static void +query_user (GsdIdentityPlugin *self, + GsdIdentity *identity, + GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query, + GcrPrompt *prompt, + GCancellable *cancellable) +{ + SystemPromptRequest *request; + char *prompt_text; + GsdIdentityQueryMode query_mode; + char *description; + char *name; + + g_assert (GSD_IS_KERBEROS_IDENTITY (identity)); + + gcr_prompt_set_title (prompt, _("Sign In to Realm")); + + name = gsd_identity_manager_name_identity (self->priv->identity_manager, + identity); + + description = g_strdup_printf (_("The network realm %s needs some information to sign you in."), name); + g_free (name); + + gcr_prompt_set_description (prompt, description); + g_free (description); + + prompt_text = gsd_identity_query_get_prompt (inquiry, query); + gcr_prompt_set_message (prompt, prompt_text); + g_free (prompt_text); + + request = system_prompt_request_new (self, + GCR_SYSTEM_PROMPT (prompt), + identity, + inquiry, + query, + cancellable); + + g_signal_connect (G_OBJECT (self->priv->identity_manager), + "identity-refreshed", + G_CALLBACK (close_system_prompt), + request); + + query_mode = gsd_identity_query_get_mode (inquiry, query); + + switch (query_mode) { + case GSD_IDENTITY_QUERY_MODE_INVISIBLE: + gcr_prompt_password_async (prompt, + cancellable, + (GAsyncReadyCallback) + on_password_system_prompt_answered, + request); + break; + case GSD_IDENTITY_QUERY_MODE_VISIBLE: + gcr_prompt_password_async (prompt, + cancellable, + (GAsyncReadyCallback) + on_password_system_prompt_answered, + request); + break; + } +} + +typedef struct { + GsdIdentityPlugin *plugin; + GsdIdentityInquiry *inquiry; + GCancellable *cancellable; +} SystemPromptOpenRequest; + +static SystemPromptOpenRequest * +system_prompt_open_request_new (GsdIdentityPlugin *plugin, + GsdIdentityInquiry *inquiry, + GCancellable *cancellable) +{ + SystemPromptOpenRequest *data; + + data = g_slice_new0 (SystemPromptOpenRequest); + + data->plugin = plugin; + data->inquiry = g_object_ref (inquiry); + data->cancellable = g_object_ref (cancellable); + + return data; +} + +static void +system_prompt_open_request_free (SystemPromptOpenRequest *data) +{ + g_clear_object (&data->inquiry); + g_clear_object (&data->cancellable); + g_slice_free (SystemPromptOpenRequest, data); +} + +static void +on_system_prompt_open (GcrSystemPrompt *system_prompt, + GAsyncResult *result, + SystemPromptOpenRequest *request) +{ + GsdIdentityPlugin *self = request->plugin; + GsdIdentityInquiry *inquiry = request->inquiry; + GCancellable *cancellable = request->cancellable; + GsdIdentity *identity; + GsdIdentityQuery *query; + GcrPrompt *prompt; + GError *error; + GsdIdentityInquiryIter iter; + + error = NULL; + prompt = gcr_system_prompt_open_finish (result, &error); + + if (prompt == NULL) { + if (error != NULL) { + g_debug ("GsdIdentityPlugin: could not open system prompt: %s", + error->message); + g_error_free (error); + } + return; + } + + identity = gsd_identity_inquiry_get_identity (inquiry); + gsd_identity_inquiry_iter_init (&iter, inquiry); + while ((query = gsd_identity_inquiry_iter_next (&iter, inquiry)) != NULL) { + query_user (self, identity, inquiry, query, prompt, cancellable); + } + + system_prompt_open_request_free (request); +} + +static void +on_identity_inquiry (GsdIdentityInquiry *inquiry, + GCancellable *cancellable, + GsdIdentityPlugin *self) +{ + SystemPromptOpenRequest *request; + + request = system_prompt_open_request_new (self, inquiry, cancellable); + gcr_system_prompt_open_async (-1, + cancellable, + (GAsyncReadyCallback) + on_system_prompt_open, + request); +} + +static void +on_sign_in_clicked (NotifyNotification *notification, + const char *acition_id, + SignInRequest *request) +{ + GsdIdentityPlugin *self = request->plugin; + GsdIdentity *identity = request->identity; + const char *identifier; + + identifier = gsd_identity_get_identifier (identity); + gsd_identity_manager_sign_identity_in (self->priv->identity_manager, + identifier, + (GsdIdentityInquiryFunc) + on_identity_inquiry, + self, + request->cancellable, + (GAsyncReadyCallback) + on_identity_signed_in, + self); +} + +static void +close_notification (GCancellable *cancellable, + NotifyNotification *notification) +{ + notify_notification_close (notification, NULL); +} + +static void +cancel_sign_in (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + SignInRequest *data) +{ + if (data->cancellable == NULL) { + return; + } + + if (!g_cancellable_is_cancelled (data->cancellable)) { + g_cancellable_cancel (data->cancellable); + } + + g_clear_object (&data->cancellable); +} + +static void +ask_to_sign_in (GsdIdentityPlugin *self, + GsdIdentity *identity) +{ + NotifyNotification *notification; + char *name; + char *description; + SignInRequest *request; + GCancellable *cancellable; + + request = g_object_get_data (G_OBJECT (identity), "sign-in-request"); + + if (request != NULL) { + if (!g_cancellable_is_cancelled (request->cancellable)) { + g_cancellable_cancel (request->cancellable); + } + } + + g_debug ("GsdIdentityPlugin: asking to sign back in"); + + name = gsd_identity_manager_name_identity (self->priv->identity_manager, + identity); + if (gsd_identity_is_signed_in (identity)) { + description = g_strdup_printf (_("The network realm %s will soon be inaccessible."), + name); + } else { + description = g_strdup_printf (_("The network realm %s is now inaccessible."), + name); + } + g_free (name); + + notification = notify_notification_new (_("Realm Access"), + description, + "dialog-password-symbolic"); + g_free (description); + notify_notification_set_app_name (notification, _("Network Realm")); + + cancellable = g_cancellable_new (); + + request = sign_in_request_new (self, identity, notification, cancellable); + + g_object_set_data (G_OBJECT (identity), "sign-in-request", request); + + g_cancellable_connect (cancellable, + G_CALLBACK (close_notification), + notification, + NULL); + g_signal_connect_swapped (G_OBJECT (notification), + "closed", + G_CALLBACK (sign_in_request_free), + request); + + request->refreshed_signal_id = g_signal_connect (G_OBJECT (self->priv->identity_manager), + "identity-refreshed", + G_CALLBACK (cancel_sign_in), + request); + + notify_notification_add_action (notification, + "sign-in", + _("Sign In"), + (NotifyActionCallback) + on_sign_in_clicked, + request, + NULL); + + notify_notification_show (notification, NULL); +} + +static void +on_identity_expiring (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityPlugin *self) +{ + g_debug ("GsdIdentityPlugin: identity expiring"); + ask_to_sign_in (self, identity); +} + +static void +on_identity_expired (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityPlugin *self) +{ + g_debug ("GsdIdentityPlugin: identity expired"); + ask_to_sign_in (self, identity); +} + +static void +on_identity_signed_in_for_service (GsdIdentityManager *manager, + GAsyncResult *result, + GSimpleAsyncResult *service_result) +{ + GError *error; + GsdIdentity *identity; + + error = NULL; + identity = gsd_identity_manager_sign_identity_in_finish (manager, + result, + &error); + + if (error != NULL) { + g_debug ("GsdIdentityPlugin: Identity could not be signed in: %s", + error->message); + g_simple_async_result_take_error (service_result, error); + + } else { + g_debug ("GsdIdentityPlugin: Identity %s signed in", + gsd_identity_get_identifier (identity)); + g_simple_async_result_set_op_res_gpointer (service_result, + g_object_ref (identity), + (GDestroyNotify) + g_object_unref); + } + g_simple_async_result_complete (service_result); + +} + +static void +handle_identity_service_sign_in_signal (GsdIdentityService *service, + const char *identifier, + GVariant *details, + GCancellable *cancellable, + GSimpleAsyncResult *result, + GsdIdentityPlugin *self) +{ + gsd_identity_manager_sign_identity_in (self->priv->identity_manager, + identifier, + (GsdIdentityInquiryFunc) + on_identity_inquiry, + self, + cancellable, + (GAsyncReadyCallback) + on_identity_signed_in_for_service, + result); +} + +static void +on_identity_signed_out_for_service (GsdIdentityManager *manager, + GAsyncResult *result, + GSimpleAsyncResult *service_result) +{ + GError *error; + + error = NULL; + gsd_identity_manager_sign_identity_out_finish (manager, result, &error); + + if (error != NULL) { + g_debug ("GsdIdentityPlugin: Identity could not be signed out: %s", + error->message); + g_simple_async_result_take_error (service_result, error); + + } + + g_simple_async_result_complete (service_result); + +} + +static void +on_got_identity_for_service (GsdIdentityManager *manager, + GAsyncResult *result, + GSimpleAsyncResult *service_result) +{ + GError *error; + GsdIdentity *identity; + + error = NULL; + identity = gsd_identity_manager_get_identity_finish (manager, + result, + &error); + + if (error != NULL) { + g_debug ("GsdIdentityPlugin: Identity could not be signed in: %s", + error->message); + g_simple_async_result_take_error (service_result, error); + g_simple_async_result_complete (service_result); + return; + } + + gsd_identity_manager_sign_identity_out (manager, + identity, + NULL, + (GAsyncReadyCallback) + on_identity_signed_out_for_service, + service_result); +} + +static void +handle_identity_service_sign_out_signal (GsdIdentityService *service, + const char *identifier, + GCancellable *cancellable, + GSimpleAsyncResult *result, + GsdIdentityPlugin *self) +{ + gsd_identity_manager_get_identity (self->priv->identity_manager, + identifier, + cancellable, + (GAsyncReadyCallback) + on_got_identity_for_service, + result); +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *unique_name, + GsdIdentityPlugin *self) +{ + g_debug ("GsdIdentityPlugin: Connected to session bus"); + + if (self->priv->identity_service == NULL) { + GError *error; + + error = NULL; + self->priv->identity_service = gsd_identity_service_new (connection, + NULL, + &error); + + if (self->priv->identity_service == NULL) { + g_warning ("Could not create identity service: %s", + error->message); + } + + g_signal_connect (G_OBJECT (self->priv->identity_service), + "handle-sign-in", + G_CALLBACK (handle_identity_service_sign_in_signal), + self); + + g_signal_connect (G_OBJECT (self->priv->identity_service), + "handle-sign-out", + G_CALLBACK (handle_identity_service_sign_out_signal), + self); + } +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + GsdIdentityPlugin *self) +{ + if (g_strcmp0 (name, "org.gnome.Identity") == 0) { + g_debug ("GsdIdentityPlugin: Acquired name org.gnome.Identity"); + } +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + GsdIdentityPlugin *self) +{ + if (g_strcmp0 (name, "org.gnome.Identity") == 0) { + g_debug ("GsdIdentityPlugin: Lost name org.gnome.Identity"); + } +} + +static void +impl_activate (GnomeSettingsPlugin *plugin) +{ + GsdIdentityPlugin *self = GSD_IDENTITY_PLUGIN (plugin); + + g_debug ("GsdIdentityPlugin: Activating identity plugin"); + + if (self->priv->identity_manager == NULL) { + GError *error; + + self->priv->identity_manager = gsd_kerberos_identity_manager_new (NULL, &error); + + if (self->priv->identity_manager == NULL) { + g_warning ("Could not create kerberos identity manager: %s", + error->message); + g_error_free (error); + g_clear_object (&self->priv->identity_manager); + + return; + } + + g_signal_connect (G_OBJECT (self->priv->identity_manager), + "identity-needs-renewal", + G_CALLBACK (on_identity_needs_renewal), + self); + g_signal_connect (G_OBJECT (self->priv->identity_manager), + "identity-expiring", + G_CALLBACK (on_identity_expiring), + self); + g_signal_connect (G_OBJECT (self->priv->identity_manager), + "identity-expired", + G_CALLBACK (on_identity_expired), + self); + } + + self->priv->bus_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gnome.Identity", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE, + (GBusAcquiredCallback) + on_bus_acquired, + (GBusNameAcquiredCallback) + on_name_acquired, + (GBusNameAcquiredCallback) + on_name_lost, + self, + NULL); + +} + +static void +impl_deactivate (GnomeSettingsPlugin *plugin) +{ + GsdIdentityPlugin *self = GSD_IDENTITY_PLUGIN (plugin); + + g_debug ("GsdIdentityPlugin: Deactivating identity plugin"); + + if (self->priv->identity_manager != NULL) { + g_signal_handlers_disconnect_by_func (self, on_identity_needs_renewal, self); + g_signal_handlers_disconnect_by_func (self, on_identity_expiring, self); + g_signal_handlers_disconnect_by_func (self, on_identity_expired, self); + g_clear_object (&self->priv->identity_manager); + } + + g_clear_object (&self->priv->identity_service); +} + +static void +gsd_identity_plugin_class_init (GsdIdentityPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GnomeSettingsPluginClass *plugin_class = GNOME_SETTINGS_PLUGIN_CLASS (klass); + + object_class->finalize = gsd_identity_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + + g_type_class_add_private (klass, sizeof (GsdIdentityPluginPrivate)); +} + +static void +gsd_identity_plugin_class_finalize (GsdIdentityPluginClass *klass) +{ +} diff --git a/plugins/identity/gsd-identity-plugin.h b/plugins/identity/gsd-identity-plugin.h new file mode 100644 index 00000000..ddfd9d4b --- /dev/null +++ b/plugins/identity/gsd-identity-plugin.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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 __GSD_IDENTITY_PLUGIN_H__ +#define __GSD_IDENTITY_PLUGIN_H__ + +#include +#include +#include + +#include "gnome-settings-plugin.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY_PLUGIN (gsd_identity_plugin_get_type ()) +#define GSD_IDENTITY_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_IDENTITY_PLUGIN, GsdIdentityPlugin)) +#define GSD_IDENTITY_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GSD_TYPE_IDENTITY_PLUGIN, GsdIdentityPluginClass)) +#define GSD_IS_IDENTITY_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_IDENTITY_PLUGIN)) +#define GSD_IS_IDENTITY_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_IDENTITY_PLUGIN)) +#define GSD_IDENTITY_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_IDENTITY_PLUGIN, GsdIdentityPluginClass)) + +typedef struct GsdIdentityPluginPrivate GsdIdentityPluginPrivate; + +typedef struct +{ + GnomeSettingsPlugin parent; + GsdIdentityPluginPrivate *priv; +} GsdIdentityPlugin; + +typedef struct +{ + GnomeSettingsPluginClass parent_class; +} GsdIdentityPluginClass; + +GType gsd_identity_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gnome_settings_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GSD_IDENTITY_PLUGIN_H__ */ diff --git a/plugins/identity/gsd-identity-service.c b/plugins/identity/gsd-identity-service.c new file mode 100644 index 00000000..965865e1 --- /dev/null +++ b/plugins/identity/gsd-identity-service.c @@ -0,0 +1,674 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#include "config.h" + +#include "gsd-identity-service.h" +#include "org.gnome.Identity.h" +#include "gsd-identity-manager.h" +#include "gsd-kerberos-identity.h" +#include "gsd-kerberos-identity-manager.h" +#include "gsd-identity-enum-types.h" + +#include +#include +#include + +struct _GsdIdentityServicePrivate +{ + GDBusConnection *bus_connection; + GDBusObjectManagerServer *object_manager_server; + GsdIdentityServiceManager *server_manager; + GsdIdentityManager *identity_manager; + GCancellable *cancellable; +}; + +enum { + PROP_0, + PROP_BUS_CONNECTION +}; + +enum { + HANDLE_SIGN_IN, + HANDLE_SIGN_OUT, + NUMBER_OF_SIGNALS + +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +static void gsd_identity_service_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *param_spec); +static void gsd_identity_service_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec); +static void initable_interface_init (GInitableIface *interface); +G_DEFINE_TYPE_WITH_CODE (GsdIdentityService, + gsd_identity_service, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_interface_init)); +static char * +escape_object_path_component (const char *data, + gsize length) +{ + const char *p; + char *object_path; + GString *string; + + g_return_val_if_fail (data != NULL, NULL); + + string = g_string_sized_new ((length + 1) * 6); + + for (p = data; *p != '\0'; p++) + { + guchar character; + + character = (guchar) *p; + + if (((character >= ((guchar) 'a')) && + (character <= ((guchar) 'z'))) || + ((character >= ((guchar) 'A')) && + (character <= ((guchar) 'Z'))) || + ((character >= ((guchar) '0')) && + (character <= ((guchar) '9')))) + { + g_string_append_c (string, (char) character); + continue; + } + + g_string_append_printf (string, "_%x_", character); + } + + object_path = string->str; + + g_string_free (string, FALSE); + + return object_path; +} + +static char * +get_object_path_for_identity (GsdIdentityService *self, + GsdIdentity *identity) +{ + char **components; + GString *object_path; + int i; + + components = gsd_identity_get_identifier_components (identity); + object_path = g_string_new ("/org/gnome/Identity/Manager"); + + for (i = 0; components[i] != NULL; i++) { + char *escaped_component; + + g_string_append_c (object_path, '/'); + escaped_component = escape_object_path_component (components[i], + (gsize) strlen (components[i])); + + g_string_append (object_path, escaped_component); + + g_free (escaped_component); + } + g_strfreev (components); + + return g_string_free (object_path, FALSE); +} + +static char * +export_identity (GsdIdentityService *self, + GsdIdentity *identity) +{ + char *object_path; + GDBusObjectSkeleton *object; + GDBusInterfaceSkeleton *interface; + + object_path = get_object_path_for_identity (self, identity); + object = G_DBUS_OBJECT_SKELETON (gsd_identity_service_object_skeleton_new (object_path)); + interface = G_DBUS_INTERFACE_SKELETON (gsd_identity_service_org_gnome_identity_skeleton_new ()); + + g_object_bind_property (G_OBJECT (identity), + "identifier", + G_OBJECT (interface), + "identifier", + G_BINDING_DEFAULT); + + g_object_bind_property (G_OBJECT (identity), + "expiration-timestamp", + G_OBJECT (interface), + "expiration-timestamp", + G_BINDING_DEFAULT); + + g_dbus_object_skeleton_add_interface (object, interface); + g_object_unref (interface); + + g_dbus_object_manager_server_export (self->priv->object_manager_server, + object); + g_object_unref (object); + + return object_path; +} + +static void +unexport_identity (GsdIdentityService *self, + GsdIdentity *identity) +{ + char *object_path; + + object_path = get_object_path_for_identity (self, identity); + g_dbus_object_manager_server_unexport (self->priv->object_manager_server, + object_path); + g_free (object_path); +} + +static void +on_identity_added (GsdIdentityManager *manager, + GsdIdentity *identity, + GsdIdentityService *self) +{ + export_identity (self, identity); +} + +static void +on_identity_removed (GsdIdentityManager *manager, + GsdIdentity *identity, + GsdIdentityService *self) +{ + unexport_identity (self, identity); +} + +static void +on_identity_expired (GsdIdentityManager *manager, + GsdIdentity *identity, + GsdIdentityService *self) +{ +} + +static void +on_identity_refreshed (GsdIdentityManager *manager, + GsdIdentity *identity, + GsdIdentityService *self) +{ +} + +static void +on_identity_renamed (GsdIdentityManager *manager, + GsdIdentity *identity, + GsdIdentityService *self) +{ +} + +static void +on_identities_listed (GsdIdentityManager *manager, + GAsyncResult *result, + GsdIdentityService *self) +{ + GError *error = NULL; + GList *identities, *node; + + g_signal_connect (manager, + "identity-added", + G_CALLBACK (on_identity_added), + self); + + g_signal_connect (manager, + "identity-removed", + G_CALLBACK (on_identity_removed), + self); + + g_signal_connect (manager, + "identity-expired", + G_CALLBACK (on_identity_expired), + self); + + g_signal_connect (manager, + "identity-refreshed", + G_CALLBACK (on_identity_refreshed), + self); + + g_signal_connect (manager, + "identity-renamed", + G_CALLBACK (on_identity_renamed), + self); + + identities = gsd_identity_manager_list_identities_finish (manager, + result, + &error); + + if (identities == NULL) { + if (error != NULL) { + g_warning ("UmUserPanel: Could not list identities: %s", + error->message); + g_error_free (error); + } + return; + } + + for (node = identities; node != NULL; node = node->next) { + GsdIdentity *identity = node->data; + + export_identity (self, identity); + } +} + +static void +on_sign_in_handled (GsdIdentityService *self, + GSimpleAsyncResult *result, + GDBusMethodInvocation *invocation) +{ + GError *error; + GsdIdentity *identity; + char *object_path; + + error = NULL; + if (g_simple_async_result_propagate_error (result, + &error)) { + g_dbus_method_invocation_take_error (invocation, error); + return; + } + + identity = GSD_IDENTITY (g_simple_async_result_get_op_res_gpointer (result)); + object_path = export_identity (self, identity); + g_object_unref (identity); + + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(o)", object_path)); + g_free (object_path); +} + +static gboolean +handle_sign_in (GsdIdentityServiceManager *manager, + GDBusMethodInvocation *invocation, + const char *identifier, + GVariant *details, + GsdIdentityService *self) +{ + GSimpleAsyncResult *result; + GCancellable *cancellable; + + result = g_simple_async_result_new (G_OBJECT (self), + (GAsyncReadyCallback) + on_sign_in_handled, + invocation, + handle_sign_in); + + /* FIXME: should we cancel this if the invoker falls off the bus ?*/ + cancellable = g_cancellable_new (); + g_simple_async_result_set_check_cancellable (result, cancellable); + + g_signal_emit (G_OBJECT (self), + signals[HANDLE_SIGN_IN], + 0, + identifier, + details, + cancellable, + result); + + return TRUE; +} + +static void +on_sign_out_handled (GsdIdentityService *self, + GSimpleAsyncResult *result, + GDBusMethodInvocation *invocation) +{ + GError *error; + + error = NULL; + if (g_simple_async_result_propagate_error (result, + &error)) { + g_dbus_method_invocation_take_error (invocation, error); + return; + } + + + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static gboolean +handle_sign_out (GsdIdentityServiceManager *manager, + GDBusMethodInvocation *invocation, + const char *identifier, + GsdIdentityService *self) +{ + GSimpleAsyncResult *result; + GCancellable *cancellable; + + result = g_simple_async_result_new (G_OBJECT (self), + (GAsyncReadyCallback) + on_sign_out_handled, + invocation, + handle_sign_out); + + cancellable = g_cancellable_new (); + g_simple_async_result_set_check_cancellable (result, cancellable); + + g_signal_emit (G_OBJECT (self), + signals[HANDLE_SIGN_OUT], + 0, + identifier, + cancellable, + result); + + return TRUE; +} + +static gboolean +gsd_identity_service_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsdIdentityService *self = GSD_IDENTITY_SERVICE (initable); + GsdIdentityServiceObjectSkeleton *object; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + g_return_val_if_fail (self->priv->bus_connection != NULL, FALSE); + + self->priv->object_manager_server = g_dbus_object_manager_server_new ("/org/gnome/Identity"); + + self->priv->server_manager = gsd_identity_service_manager_skeleton_new (); + g_signal_connect (self->priv->server_manager, + "handle-sign-in", + G_CALLBACK (handle_sign_in), + self); + g_signal_connect (self->priv->server_manager, + "handle-sign-out", + G_CALLBACK (handle_sign_out), + self); + + object = gsd_identity_service_object_skeleton_new ("/org/gnome/Identity/Manager"); + gsd_identity_service_object_skeleton_set_manager (object, + self->priv->server_manager); + + g_dbus_object_manager_server_export (self->priv->object_manager_server, + G_DBUS_OBJECT_SKELETON (object)); + g_object_unref (object); + + g_dbus_object_manager_server_set_connection (self->priv->object_manager_server, + self->priv->bus_connection); + + self->priv->identity_manager = gsd_kerberos_identity_manager_new (cancellable, error); + if (self->priv->identity_manager == NULL) { + return FALSE; + } + + self->priv->cancellable = g_cancellable_new (); + gsd_identity_manager_list_identities (self->priv->identity_manager, + self->priv->cancellable, + (GAsyncReadyCallback) + on_identities_listed, self); + + return TRUE; +} + +static void +initable_interface_init (GInitableIface *interface) +{ + interface->init = gsd_identity_service_initable_init; +} + +static void +gsd_identity_service_init (GsdIdentityService *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_IDENTITY_SERVICE, + GsdIdentityServicePrivate); +} + +static void +gsd_identity_service_dispose (GObject *object) +{ + GsdIdentityService *self = GSD_IDENTITY_SERVICE (object); + + g_clear_object (&self->priv->bus_connection); + g_clear_object (&self->priv->object_manager_server); + g_clear_object (&self->priv->server_manager); + g_clear_object (&self->priv->identity_manager); + + g_cancellable_cancel (self->priv->cancellable); + g_clear_object (&self->priv->cancellable); + + G_OBJECT_CLASS (gsd_identity_service_parent_class)->dispose (object); +} + +static void +gsd_identity_service_finalize (GObject *object) +{ + G_OBJECT_CLASS (gsd_identity_service_parent_class)->finalize (object); +} + +static void +gsd_identity_service_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *param_spec) +{ + GsdIdentityService *self = GSD_IDENTITY_SERVICE (object); + + switch (property_id) { + case PROP_BUS_CONNECTION: + self->priv->bus_connection = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); + break; + } +} + +static void +gsd_identity_service_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec) +{ + GsdIdentityService *self = GSD_IDENTITY_SERVICE (object); + + switch (property_id) { + case PROP_BUS_CONNECTION: + g_value_set_object (value, self->priv->bus_connection); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); + break; + } +} + +static char * +dashed_string_to_studly_caps (const char *dashed_string) +{ + char *studly_string;; + size_t studly_string_length; + size_t i; + + i = 0; + + studly_string = g_strdup (dashed_string); + studly_string_length = strlen (studly_string); + + studly_string[i] = g_ascii_toupper (studly_string[i]); + i++; + + while (i < studly_string_length) { + if (studly_string[i] == '-' || studly_string[i] == '_') { + memmove (studly_string + i, + studly_string + i + 1, + studly_string_length - i - 1); + studly_string_length--; + if (g_ascii_isalpha (studly_string[i])) { + studly_string[i] = g_ascii_toupper (studly_string[i]); + } + } + i++; + } + studly_string[studly_string_length] = '\0'; + + return studly_string; +} + +static char * +dashed_string_to_dbus_error_string (const char *dashed_string, + const char *old_prefix, + const char *new_prefix, + const char *suffix) +{ + char *studly_suffix; + char *dbus_error_string; + size_t dbus_error_string_length; + size_t i; + + i = 0; + + if (g_str_has_prefix (dashed_string, old_prefix) && + (dashed_string[strlen(old_prefix)] == '-' || + dashed_string[strlen(old_prefix)] == '_')) { + dashed_string += strlen (old_prefix) + 1; + } + + studly_suffix = dashed_string_to_studly_caps (suffix); + dbus_error_string = g_strdup_printf ("%s.%s.%s", new_prefix, dashed_string, studly_suffix); + g_free (studly_suffix); + i += strlen (new_prefix) + 1; + + dbus_error_string_length = strlen (dbus_error_string); + + dbus_error_string[i] = g_ascii_toupper (dbus_error_string[i]); + i++; + + while (i < dbus_error_string_length) { + if (dbus_error_string[i] == '_' || dbus_error_string[i] == '-') { + dbus_error_string[i] = '.'; + + if (g_ascii_isalpha (dbus_error_string[i + 1])) { + dbus_error_string[i + 1] = g_ascii_toupper (dbus_error_string[i + 1]); + } + } + + i++; + } + + return dbus_error_string; +} + +static void +register_error_domain (GQuark error_domain, + GType error_enum) +{ + const char *error_domain_string; + char *type_name; + GType type; + GTypeClass *type_class; + GEnumClass *enum_class; + guint i; + + error_domain_string = g_quark_to_string (error_domain); + type_name = dashed_string_to_studly_caps (error_domain_string); + type = g_type_from_name (type_name); + type_class = g_type_class_ref (type); + enum_class = G_ENUM_CLASS (type_class); + + for (i = 0; i < enum_class->n_values; i++) { + char *dbus_error_string; + + dbus_error_string = dashed_string_to_dbus_error_string (error_domain_string, + "gsd", + "org.gnome", + enum_class->values[i].value_nick); + + g_debug ("GsdIdentityService: Registering dbus error %s", dbus_error_string); + g_dbus_error_register_error (error_domain, + enum_class->values[i].value, + dbus_error_string); + g_free (dbus_error_string); + } + + g_type_class_unref (type_class); +} + +static void +gsd_identity_service_class_init (GsdIdentityServiceClass *service_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (service_class); + GParamSpec *param_spec; + + object_class->dispose = gsd_identity_service_dispose; + object_class->finalize = gsd_identity_service_finalize; + object_class->set_property = gsd_identity_service_set_property; + object_class->get_property = gsd_identity_service_get_property; + + signals[HANDLE_SIGN_IN] = g_signal_new ("handle-sign-in", + G_TYPE_FROM_CLASS (service_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityServiceClass, handle_sign_in), + NULL, NULL, NULL, + G_TYPE_NONE, 4, + G_TYPE_STRING, + G_TYPE_VARIANT, + G_TYPE_CANCELLABLE, + G_TYPE_SIMPLE_ASYNC_RESULT); + signals[HANDLE_SIGN_OUT] = g_signal_new ("handle-sign-out", + G_TYPE_FROM_CLASS (service_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsdIdentityServiceClass, handle_sign_out), + NULL, NULL, NULL, + G_TYPE_NONE, 3, + G_TYPE_STRING, + G_TYPE_CANCELLABLE, + G_TYPE_SIMPLE_ASYNC_RESULT); + param_spec = g_param_spec_object ("bus-connection", + "Bus Connection", + "bus connection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_BUS_CONNECTION, param_spec); + register_error_domain (GSD_IDENTITY_MANAGER_ERROR, + GSD_TYPE_IDENTITY_MANAGER_ERROR); + register_error_domain (GSD_IDENTITY_ERROR, + GSD_TYPE_IDENTITY_ERROR); + + g_type_class_add_private (service_class, sizeof (GsdIdentityServicePrivate)); +} + +GsdIdentityService * +gsd_identity_service_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error) +{ + GObject *object; + + object = g_object_new (GSD_TYPE_IDENTITY_SERVICE, + "bus-connection", connection, + NULL); + + if (!g_initable_init (G_INITABLE (object), cancellable, error)) { + return NULL; + } + + return GSD_IDENTITY_SERVICE (object); +} + diff --git a/plugins/identity/gsd-identity-service.h b/plugins/identity/gsd-identity-service.h new file mode 100644 index 00000000..913f229e --- /dev/null +++ b/plugins/identity/gsd-identity-service.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_SERVICE_H__ +#define __GSD_IDENTITY_SERVICE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY_SERVICE (gsd_identity_service_get_type ()) +#define GSD_IDENTITY_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GSD_TYPE_IDENTITY_SERVICE, GsdIdentityService)) +#define GSD_IDENTITY_SERVICE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GSD_TYPE_IDENTITY_SERVICE, GsdIdentityServiceClass)) +#define GSD_IS_IDENTITY_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GSD_TYPE_IDENTITY_SERVICE)) +#define GSD_IS_IDENTITY_SERVICE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GSD_TYPE_IDENTITY_SERVICE)) +#define GSD_IDENTITY_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSD_TYPE_IDENTITY_SERVICE, GsdIdentityServiceClass)) + +typedef struct _GsdIdentityService GsdIdentityService; +typedef struct _GsdIdentityServiceClass GsdIdentityServiceClass; +typedef struct _GsdIdentityServicePrivate GsdIdentityServicePrivate; + +struct _GsdIdentityService +{ + GObject parent_instance; + GsdIdentityServicePrivate *priv; +}; + +struct _GsdIdentityServiceClass +{ + GObjectClass parent_class; + + void (* handle_sign_in) (GsdIdentityService *server, + const char *identifier, + GVariant *details, + GCancellable *cancellable, + GSimpleAsyncResult *result); + + void (* handle_sign_out) (GsdIdentityService *server, + const char *identifier, + GCancellable *cancellable, + GSimpleAsyncResult *result); + +}; + +GType gsd_identity_service_get_type (void); +GsdIdentityService* gsd_identity_service_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* __GSD_IDENTITY_SERVICE_H__ */ diff --git a/plugins/identity/gsd-identity-test.c b/plugins/identity/gsd-identity-test.c new file mode 100644 index 00000000..21871e77 --- /dev/null +++ b/plugins/identity/gsd-identity-test.c @@ -0,0 +1,331 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#include "config.h" + +#include "gsd-identity-test.h" +#include "gsd-kerberos-identity-manager.h" +#include "gsd-kerberos-identity.h" +#include "gsd-identity-inquiry.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct _GsdIdentityTestPrivate +{ + GsdIdentityManager *identity_manager; + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (GsdIdentityTest, gsd_identity_test, G_TYPE_OBJECT); + +static void +gsd_identity_test_init (GsdIdentityTest *self) +{ + GError *error; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_KERBEROS_IDENTITY_MANAGER, + GsdIdentityTestPrivate); + + error = NULL; + self->priv->identity_manager = gsd_kerberos_identity_manager_new (NULL, &error); + + if (self->priv->identity_manager == NULL) { + g_warning ("identity manager could not be created: %s", error->message); + } + +} + +static void +gsd_identity_test_dispose (GObject *object) +{ + GsdIdentityTest *self = GSD_IDENTITY_TEST (object); + + g_clear_object (&self->priv->identity_manager); + + G_OBJECT_CLASS (gsd_identity_test_parent_class)->dispose (object); +} + +static void +gsd_identity_test_finalize (GObject *object) +{ + G_OBJECT_CLASS (gsd_identity_test_parent_class)->finalize (object); +} + +static void +gsd_identity_test_class_init (GsdIdentityTestClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsd_identity_test_dispose; + object_class->finalize = gsd_identity_test_finalize; + + g_type_class_add_private (klass, sizeof (GsdIdentityTestPrivate)); +} + +GsdIdentityTest * +gsd_identity_test_new (void) +{ + GObject *object = g_object_new (GSD_TYPE_IDENTITY_TEST, NULL); + + return GSD_IDENTITY_TEST (object); +} + +static void +on_identity_renewed (GsdIdentityManager *identity_manager, + GAsyncResult *result, + GsdIdentityTest *test) +{ + GError *error; + + error = NULL; + gsd_identity_manager_renew_identity_finish (identity_manager, + result, + &error); + + if (error != NULL) { + g_warning ("Could not renew identity: %s", + error->message); + g_error_free (error); + return; + } + + g_message ("identity renewed"); +} + +static void +on_identity_needs_renewal (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityTest *test) +{ + g_message ("identity needs renewal"); + gsd_identity_manager_renew_identity (identity_manager, + identity, + test->priv->cancellable, + (GAsyncReadyCallback) + on_identity_renewed, + test); +} + + +typedef struct +{ + GsdIdentityTest *test; + const char *identifier; +} SignInRequest; + +static void +on_identity_inquiry (GsdIdentityInquiry *inquiry, + GCancellable *cancellable, + GsdIdentityTest *test) +{ + GsdIdentityInquiryIter iter; + GsdIdentityQuery *query; + char *name, *banner; + int fd; + + if (g_cancellable_is_cancelled (test->priv->cancellable)) { + return; + } + + name = gsd_identity_inquiry_get_name (inquiry); + g_message ("name: %s", name); + g_free (name); + + banner = gsd_identity_inquiry_get_banner (inquiry); + g_message ("banner: %s", banner); + g_free (banner); + + fd = g_open ("/dev/tty", O_RDWR); + gsd_identity_inquiry_iter_init (&iter, inquiry); + while ((query = gsd_identity_inquiry_iter_next (&iter, inquiry)) != NULL) { + char *prompt; + char answer[256] = ""; + ssize_t bytes_read; + GsdIdentityQueryMode mode; + + if (g_cancellable_is_cancelled (test->priv->cancellable)) { + break; + } + + prompt = gsd_identity_query_get_prompt (inquiry, query); + g_message ("prompt: %s", prompt); + g_free (prompt); + + mode = gsd_identity_query_get_mode (inquiry, query); + if (mode == GSD_IDENTITY_QUERY_MODE_INVISIBLE) { + /* don't print user input to screen */ + g_print ("\033[12h"); + } + + bytes_read = read (fd, answer, sizeof (answer) - 1); + + if (mode == GSD_IDENTITY_QUERY_MODE_INVISIBLE) { + /* restore visible input if we turned it off */ + g_print ("\033[12l"); + } + + if (g_cancellable_is_cancelled (test->priv->cancellable)) { + break; + } + + if (bytes_read > 0) { + /* Trim off \n */ + answer[bytes_read - 1] = '\0'; + g_message ("using password '%s'", answer); + gsd_identity_inquiry_answer_query (inquiry, query, answer); + } + } + close (fd); +} + +static void +on_identity_signed_in (GsdIdentityManager *identity_manager, + GAsyncResult *result, + SignInRequest *request) +{ + GError *error; + + error = NULL; + gsd_identity_manager_sign_identity_in_finish (identity_manager, + result, + &error); + + if (error != NULL) { + g_warning ("Could not sign-in identity %s: %s", + request->identifier, error->message); + g_error_free (error); + g_object_unref (request->test); + g_slice_free (SignInRequest, request); + return; + } + g_message ("identity %s signed in", request->identifier); + + g_object_unref (request->test); + g_slice_free (SignInRequest, request); +} + +static void +sign_in (GsdIdentityTest *test, + const char *identifier) +{ + SignInRequest *request; + + request = g_slice_new (SignInRequest); + request->test = g_object_ref (test); + request->identifier = identifier; + + gsd_identity_manager_sign_identity_in (test->priv->identity_manager, + identifier, + (GsdIdentityInquiryFunc) + on_identity_inquiry, + test, + test->priv->cancellable, + (GAsyncReadyCallback) + on_identity_signed_in, + request); +} + +static void +on_identity_expiring (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityTest *test) +{ + const char *identifier; + + g_message ("identity about to expire"); + + identifier = gsd_identity_get_identifier (identity); + sign_in (test, identifier); +} + +static void +on_identity_expired (GsdIdentityManager *identity_manager, + GsdIdentity *identity, + GsdIdentityTest *test) +{ + const char *identifier; + + g_message ("identity expired"); + + identifier = gsd_identity_get_identifier (identity); + sign_in (test, identifier); +} + +void +gsd_identity_test_start (GsdIdentityTest *test, + GError **error) +{ + const char *principal_name; + + if (!g_cancellable_is_cancelled (test->priv->cancellable)) { + g_warning ("identity test started more than once"); + g_cancellable_cancel (test->priv->cancellable); + g_clear_object (&test->priv->cancellable); + } + + test->priv->cancellable = g_cancellable_new (); + + g_signal_connect (G_OBJECT (test), + "identity-needs-renewal", + G_CALLBACK (on_identity_needs_renewal), + NULL); + g_signal_connect (G_OBJECT (test), + "identity-expiring", + G_CALLBACK (on_identity_expiring), + NULL); + g_signal_connect (G_OBJECT (test), + "identity-expired", + G_CALLBACK (on_identity_expired), + NULL); + + principal_name = g_getenv ("GSD_IDENTITY_TEST_PRINCIPAL"); + if (principal_name != NULL) { + sign_in (test, principal_name); + } +} + +void +gsd_identity_test_stop (GsdIdentityTest *test) +{ + + g_signal_handlers_disconnect_by_func (G_OBJECT (test), + G_CALLBACK (on_identity_needs_renewal), + NULL); + g_signal_handlers_disconnect_by_func (G_OBJECT (test), + G_CALLBACK (on_identity_expiring), + NULL); + g_signal_handlers_disconnect_by_func (G_OBJECT (test), + G_CALLBACK (on_identity_expired), + NULL); + + g_cancellable_cancel (test->priv->cancellable); +} diff --git a/plugins/identity/gsd-identity-test.h b/plugins/identity/gsd-identity-test.h new file mode 100644 index 00000000..292e094e --- /dev/null +++ b/plugins/identity/gsd-identity-test.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_TEST_H__ +#define __GSD_IDENTITY_TEST_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY_TEST (gsd_identity_test_get_type ()) +#define GSD_IDENTITY_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GSD_TYPE_IDENTITY_TEST, GsdIdentityTest)) +#define GSD_IDENTITY_TEST_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GSD_TYPE_IDENTITY_TEST, GsdIdentityTestClass)) +#define GSD_IS_IDENTITY_TEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GSD_TYPE_IDENTITY_TEST)) +#define GSD_IS_IDENTITY_TEST_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GSD_TYPE_IDENTITY_TEST)) +#define GSD_IDENTITY_TEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSD_TYPE_IDENTITY_TEST, GsdIdentityTestClass)) + +typedef struct _GsdIdentityTest GsdIdentityTest; +typedef struct _GsdIdentityTestClass GsdIdentityTestClass; +typedef struct _GsdIdentityTestPrivate GsdIdentityTestPrivate; + +struct _GsdIdentityTest +{ + GObject parent_instance; + GsdIdentityTestPrivate *priv; +}; + +struct _GsdIdentityTestClass +{ + GObjectClass parent_class; +}; + +GType gsd_identity_test_get_type (void); +GsdIdentityTest* gsd_identity_test_new (void); + +void gsd_identity_test_start (GsdIdentityTest *test, + GError **error); +void gsd_identity_test_stop (GsdIdentityTest *test); +G_END_DECLS + +#endif /* __GSD_IDENTITY_TEST_H__ */ diff --git a/plugins/identity/gsd-identity.c b/plugins/identity/gsd-identity.c new file mode 100644 index 00000000..2aaf014d --- /dev/null +++ b/plugins/identity/gsd-identity.c @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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 "config.h" + +#include +#include + +#include "gsd-identity.h" + +G_DEFINE_INTERFACE (GsdIdentity, gsd_identity, G_TYPE_OBJECT); + +static void +gsd_identity_default_init (GsdIdentityInterface *interface) +{ + g_object_interface_install_property (interface, + g_param_spec_string ("identifier", + "identifier", + "identifier", + NULL, + G_PARAM_READABLE)); + g_object_interface_install_property (interface, + g_param_spec_boolean ("is-signed-in", + "Is signed in", + "Whether or not identity is currently signed in", + FALSE, + G_PARAM_READABLE)); + g_object_interface_install_property (interface, + g_param_spec_int64 ("expiration-timestamp", + "Expiration Timestamp", + "A timestamp of when the identities credentials expire", + -1, + G_MAXINT64, + -1, + G_PARAM_READABLE)); +} + +GQuark +gsd_identity_error_quark (void) +{ + static GQuark error_quark = 0; + + if (error_quark == 0) { + error_quark = g_quark_from_static_string ("gsd-identity-error"); + } + + return error_quark; +} + +char ** +gsd_identity_get_identifier_components (GsdIdentity *self) +{ + return GSD_IDENTITY_GET_IFACE (self)->get_identifier_components (self); +} + +const char * +gsd_identity_get_identifier (GsdIdentity *self) +{ + return GSD_IDENTITY_GET_IFACE (self)->get_identifier (self); +} + +gboolean +gsd_identity_is_signed_in (GsdIdentity *self) +{ + return GSD_IDENTITY_GET_IFACE (self)->is_signed_in (self); +} diff --git a/plugins/identity/gsd-identity.h b/plugins/identity/gsd-identity.h new file mode 100644 index 00000000..f4b1293e --- /dev/null +++ b/plugins/identity/gsd-identity.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_IDENTITY_H__ +#define __GSD_IDENTITY_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GSD_TYPE_IDENTITY (gsd_identity_get_type ()) +#define GSD_IDENTITY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_IDENTITY, GsdIdentity)) +#define GSD_IDENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_IDENTITY, GsdIdentityInterface)) +#define GSD_IS_IDENTITY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_IDENTITY)) +#define GSD_IDENTITY_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), GSD_TYPE_IDENTITY, GsdIdentityInterface)) +#define GSD_IDENTITY_ERROR (gsd_identity_error_quark ()) + +typedef struct _GsdIdentity GsdIdentity; +typedef struct _GsdIdentityInterface GsdIdentityInterface; + +struct _GsdIdentityInterface +{ + GTypeInterface base_interface; + + const char * (* get_identifier) (GsdIdentity *identity); + char ** (* get_identifier_components) (GsdIdentity *identity); + gboolean (* is_signed_in) (GsdIdentity *identity); +}; + +typedef enum +{ + GSD_IDENTITY_ERROR_NOT_FOUND, + GSD_IDENTITY_ERROR_VERIFYING, + GSD_IDENTITY_ERROR_RENEWING, + GSD_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE, + GSD_IDENTITY_ERROR_ENUMERATING_CREDENTIALS, + GSD_IDENTITY_ERROR_ALLOCATING_CREDENTIALS, + GSD_IDENTITY_ERROR_AUTHENTICATION_FAILED, + GSD_IDENTITY_ERROR_SAVING_CREDENTIALS, + GSD_IDENTITY_ERROR_REMOVING_CREDENTIALS, + GSD_IDENTITY_ERROR_PARSING_IDENTIFIER, +} GsdIdentityError; + +GType gsd_identity_get_type (void); +GQuark gsd_identity_error_quark (void); + +const char *gsd_identity_get_identifier (GsdIdentity *identity); +char ** gsd_identity_get_identifier_components (GsdIdentity *identity); +gboolean gsd_identity_is_signed_in (GsdIdentity *identity); + + +G_END_DECLS + +#endif /* __GSD_IDENTITY_H__ */ diff --git a/plugins/identity/gsd-kerberos-identity-inquiry.c b/plugins/identity/gsd-kerberos-identity-inquiry.c new file mode 100644 index 00000000..a6a26314 --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity-inquiry.c @@ -0,0 +1,363 @@ +/* -*- Mode: C; tab-width: 8; ident-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + * + * Author: Ray Strode + */ + +#include "config.h" + +#include "gsd-kerberos-identity-inquiry.h" +#include "gsd-identity-inquiry-private.h" + +#include +#include +#include + +struct _GsdKerberosIdentityInquiryPrivate +{ + GsdIdentity *identity; + char *name; + char *banner; + GList *queries; + int number_of_queries; + int number_of_unanswered_queries; +}; + +typedef struct +{ + GsdIdentityInquiry *inquiry; + krb5_prompt *kerberos_prompt; + gboolean is_answered; +} GsdKerberosIdentityQuery; + +static void identity_inquiry_interface_init (GsdIdentityInquiryInterface *interface); +static void initable_interface_init (GInitableIface *interface); + +G_DEFINE_TYPE_WITH_CODE (GsdKerberosIdentityInquiry, + gsd_kerberos_identity_inquiry, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_interface_init) + G_IMPLEMENT_INTERFACE (GSD_TYPE_IDENTITY_INQUIRY, + identity_inquiry_interface_init)); + +static gboolean +gsd_kerberos_identity_inquiry_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + return TRUE; +} + +static void +initable_interface_init (GInitableIface *interface) +{ + interface->init = gsd_kerberos_identity_inquiry_initable_init; +} + +static GsdKerberosIdentityQuery * +gsd_kerberos_identity_query_new (GsdIdentityInquiry *inquiry, + krb5_prompt *kerberos_prompt) +{ + GsdKerberosIdentityQuery *query; + + query = g_slice_new (GsdKerberosIdentityQuery); + query->inquiry = inquiry; + query->kerberos_prompt = kerberos_prompt; + query->is_answered = FALSE; + + return query; +} + +static void +gsd_kerberos_identity_query_free (GsdKerberosIdentityQuery *query) +{ + g_slice_free (GsdKerberosIdentityQuery, query); +} + +static void +gsd_kerberos_identity_inquiry_dispose (GObject *object) +{ + GsdKerberosIdentityInquiry *self = GSD_KERBEROS_IDENTITY_INQUIRY (object); + + g_clear_object (&self->priv->identity); + g_clear_pointer (&self->priv->name, (GDestroyNotify) g_free); + g_clear_pointer (&self->priv->banner, (GDestroyNotify) g_free); + + g_list_foreach (self->priv->queries, + (GFunc) + gsd_kerberos_identity_query_free, + NULL); + g_clear_pointer (&self->priv->queries, (GDestroyNotify) g_list_free); +} + +static void +gsd_kerberos_identity_inquiry_finalize (GObject *object) +{ + G_OBJECT_CLASS (gsd_kerberos_identity_inquiry_parent_class)->finalize (object); +} + +static void +gsd_kerberos_identity_inquiry_class_init (GsdKerberosIdentityInquiryClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsd_kerberos_identity_inquiry_dispose; + object_class->finalize = gsd_kerberos_identity_inquiry_finalize; + + g_type_class_add_private (klass, sizeof (GsdKerberosIdentityInquiryPrivate)); +} + +static void +gsd_kerberos_identity_inquiry_init (GsdKerberosIdentityInquiry *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_KERBEROS_IDENTITY_INQUIRY, + GsdKerberosIdentityInquiryPrivate); +} + +GsdIdentityInquiry * +gsd_kerberos_identity_inquiry_new (GsdKerberosIdentity *identity, + const char *name, + const char *banner, + krb5_prompt prompts[], + int number_of_prompts) +{ + GObject *object; + GsdIdentityInquiry *inquiry; + GsdKerberosIdentityInquiry *self; + GError *error; + int i; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY (identity), NULL); + g_return_val_if_fail (number_of_prompts > 0, NULL); + + object = g_object_new (GSD_TYPE_KERBEROS_IDENTITY_INQUIRY, NULL); + + inquiry = GSD_IDENTITY_INQUIRY (object); + self = GSD_KERBEROS_IDENTITY_INQUIRY (object); + + /* FIXME: make these construct properties */ + self->priv->identity = g_object_ref (identity); + self->priv->name = g_strdup (name); + self->priv->banner = g_strdup (banner); + + self->priv->number_of_queries = 0; + for (i = 0; i < number_of_prompts; i++) { + GsdKerberosIdentityQuery *query; + + query = gsd_kerberos_identity_query_new (inquiry, &prompts[i]); + + self->priv->queries = g_list_prepend (self->priv->queries, query); + self->priv->number_of_queries++; + } + self->priv->queries = g_list_reverse (self->priv->queries); + + self->priv->number_of_unanswered_queries = self->priv->number_of_queries; + + error = NULL; + if (!g_initable_init (G_INITABLE (self), NULL, &error)) { + g_debug ("%s", error->message); + g_error_free (error); + g_object_unref (self); + return NULL; + } + + return inquiry; +} + +static GsdIdentity * +gsd_kerberos_identity_inquiry_get_identity (GsdIdentityInquiry *inquiry) +{ + GsdKerberosIdentityInquiry *self; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), NULL); + + self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + return self->priv->identity; +} + +static char * +gsd_kerberos_identity_inquiry_get_name (GsdIdentityInquiry *inquiry) +{ + GsdKerberosIdentityInquiry *self; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), NULL); + + self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + return g_strdup (self->priv->name); +} + +static char * +gsd_kerberos_identity_inquiry_get_banner (GsdIdentityInquiry *inquiry) +{ + GsdKerberosIdentityInquiry *self; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), NULL); + + self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + return g_strdup (self->priv->banner); +} + +static gboolean +gsd_kerberos_identity_inquiry_is_complete (GsdIdentityInquiry *inquiry) +{ + GsdKerberosIdentityInquiry *self; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), FALSE); + + self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + return self->priv->number_of_unanswered_queries == 0; +} + +static void +gsd_kerberos_identity_inquiry_mark_query_answered (GsdKerberosIdentityInquiry *self, + GsdKerberosIdentityQuery *query) +{ + if (query->is_answered) { + return; + } + + query->is_answered = TRUE; + self->priv->number_of_unanswered_queries--; + + if (self->priv->number_of_unanswered_queries == 0) { + _gsd_identity_inquiry_emit_complete (GSD_IDENTITY_INQUIRY (self)); + } +} + +static void +gsd_kerberos_identity_inquiry_answer_query (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query, + const char *answer) +{ + GsdKerberosIdentityInquiry *self; + GsdKerberosIdentityQuery *kerberos_query = (GsdKerberosIdentityQuery *) query; + + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry)); + g_return_if_fail (inquiry == kerberos_query->inquiry); + g_return_if_fail (!gsd_kerberos_identity_inquiry_is_complete (inquiry)); + + self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + inquiry = kerberos_query->inquiry; + + strncpy (kerberos_query->kerberos_prompt->reply->data, + answer, + kerberos_query->kerberos_prompt->reply->length); + kerberos_query->kerberos_prompt->reply->length = (unsigned int) strlen (kerberos_query->kerberos_prompt->reply->data); + + gsd_kerberos_identity_inquiry_mark_query_answered (self, kerberos_query); +} + +static void +gsd_kerberos_identity_inquiry_iter_init (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry) +{ + GsdKerberosIdentityInquiry *self = GSD_KERBEROS_IDENTITY_INQUIRY (inquiry); + + iter->data = self->priv->queries; +} + +static GsdIdentityQuery * +gsd_kerberos_identity_inquiry_iter_next (GsdIdentityInquiryIter *iter, + GsdIdentityInquiry *inquiry) +{ + GsdIdentityQuery *query; + GList *node; + + node = iter->data; + + if (node == NULL) { + return NULL; + } + + query = (GsdIdentityQuery *) node->data; + + node = node->next; + + iter->data = node; + + return query; +} + +static GsdIdentityQueryMode +gsd_kerberos_identity_query_get_mode (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query) +{ + GsdKerberosIdentityQuery *kerberos_query = (GsdKerberosIdentityQuery *) query; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE); + g_return_val_if_fail (inquiry == kerberos_query->inquiry, GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE); + + if (kerberos_query->kerberos_prompt->hidden) { + return GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE; + } else { + return GSD_KERBEROS_IDENTITY_QUERY_MODE_VISIBLE; + } +} + +static char * +gsd_kerberos_identity_query_get_prompt (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query) +{ + GsdKerberosIdentityQuery *kerberos_query = (GsdKerberosIdentityQuery *) query; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE); + g_return_val_if_fail (inquiry == kerberos_query->inquiry, NULL); + + return g_strdup (kerberos_query->kerberos_prompt->prompt); +} + +static gboolean +gsd_kerberos_identity_query_is_answered (GsdIdentityInquiry *inquiry, + GsdIdentityQuery *query) +{ + GsdKerberosIdentityQuery *kerberos_query = (GsdKerberosIdentityQuery *) query; + + g_return_val_if_fail (GSD_IS_KERBEROS_IDENTITY_INQUIRY (inquiry), GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE); + g_return_val_if_fail (inquiry == kerberos_query->inquiry, FALSE); + + return kerberos_query->is_answered; +} + +static void +identity_inquiry_interface_init (GsdIdentityInquiryInterface *interface) +{ + interface->get_identity = gsd_kerberos_identity_inquiry_get_identity; + interface->get_name = gsd_kerberos_identity_inquiry_get_name; + interface->get_banner = gsd_kerberos_identity_inquiry_get_banner; + interface->is_complete = gsd_kerberos_identity_inquiry_is_complete; + interface->answer_query = gsd_kerberos_identity_inquiry_answer_query; + interface->iter_init = gsd_kerberos_identity_inquiry_iter_init; + interface->iter_next = gsd_kerberos_identity_inquiry_iter_next; + interface->get_mode = gsd_kerberos_identity_query_get_mode; + interface->get_prompt = gsd_kerberos_identity_query_get_prompt; + interface->is_answered = gsd_kerberos_identity_query_is_answered; +} diff --git a/plugins/identity/gsd-kerberos-identity-inquiry.h b/plugins/identity/gsd-kerberos-identity-inquiry.h new file mode 100644 index 00000000..30c0956a --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity-inquiry.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; ident-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_KERBEROS_IDENTITY_INQUIRY_H__ +#define __GSD_KERBEROS_IDENTITY_INQUIRY_H__ + +#include + +#include +#include + +#include "gsd-identity-inquiry.h" +#include "gsd-kerberos-identity.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_KERBEROS_IDENTITY_INQUIRY (gsd_kerberos_identity_inquiry_get_type ()) +#define GSD_KERBEROS_IDENTITY_INQUIRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_KERBEROS_IDENTITY_INQUIRY, GsdKerberosIdentityInquiry)) +#define GSD_KERBEROS_IDENTITY_INQUIRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_KERBEROS_IDENTITY_INQUIRY, GsdKerberosIdentityInquiryClass)) +#define GSD_IS_KERBEROS_IDENTITY_INQUIRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_KERBEROS_IDENTITY_INQUIRY)) +#define GSD_IS_KERBEROS_IDENTITY_INQUIRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSD_TYPE_KERBEROS_IDENTITY_INQUIRY)) +#define GSD_KERBEROS_IDENTITY_INQUIRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSD_TYPE_KERBEROS_IDENTITY_INQUIRY, GsdKerberosIdentityInquiryClass)) +typedef struct _GsdKerberosIdentity GsdKerberosIdentity; +typedef struct _GsdKerberosIdentityInquiry GsdKerberosIdentityInquiry; +typedef struct _GsdKerberosIdentityInquiryClass GsdKerberosIdentityInquiryClass; +typedef struct _GsdKerberosIdentityInquiryPrivate GsdKerberosIdentityInquiryPrivate; +typedef struct _GsdKerberosIdentityInquiryIter GsdKerberosIdentityInquiryIter; + +typedef enum +{ + GSD_KERBEROS_IDENTITY_QUERY_MODE_INVISIBLE, + GSD_KERBEROS_IDENTITY_QUERY_MODE_VISIBLE +} GsdKerberosIdentityQueryMode; + +struct _GsdKerberosIdentityInquiry +{ + GObject parent; + + GsdKerberosIdentityInquiryPrivate *priv; +}; + +struct _GsdKerberosIdentityInquiryClass +{ + GObjectClass parent_class; +}; + +GType gsd_kerberos_identity_inquiry_get_type (void); + +GsdIdentityInquiry *gsd_kerberos_identity_inquiry_new (GsdKerberosIdentity *identity, + const char *name, + const char *banner, + krb5_prompt prompts[], + int number_of_prompts); + +#endif /* __GSD_KERBEROS_IDENTITY_INQUIRY_H__ */ diff --git a/plugins/identity/gsd-kerberos-identity-manager.c b/plugins/identity/gsd-kerberos-identity-manager.c new file mode 100644 index 00000000..88e6d799 --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity-manager.c @@ -0,0 +1,1590 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#include "config.h" + +#include "gsd-kerberos-identity-manager.h" +#include "gsd-identity-manager.h" +#include "gsd-identity-manager-private.h" +#include "gsd-kerberos-identity.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct _GsdKerberosIdentityManagerPrivate +{ + GHashTable *identities; + GHashTable *expired_identities; + GHashTable *identities_by_realm; + GAsyncQueue *pending_operations; + GCancellable *scheduler_cancellable; + + krb5_context kerberos_context; + GFileMonitor *credentials_cache_monitor; + gulong credentials_cache_changed_signal_id; + char *credentials_cache_type; + + GMutex scheduler_job_lock; + GCond scheduler_job_unblocked; + gboolean is_blocking_scheduler_job; + + volatile int pending_refresh_count; +}; + +typedef enum +{ + OPERATION_TYPE_REFRESH, + OPERATION_TYPE_GET_IDENTITY, + OPERATION_TYPE_LIST, + OPERATION_TYPE_RENEW, + OPERATION_TYPE_SIGN_IN, + OPERATION_TYPE_SIGN_OUT, + OPERATION_TYPE_STOP_JOB +} OperationType; + +typedef struct +{ + GCancellable *cancellable; + GsdKerberosIdentityManager *manager; + OperationType type; + GSimpleAsyncResult *result; + GIOSchedulerJob *job; + union { + GsdIdentity *identity; + struct { + const char *identifier; + GsdIdentityInquiry *inquiry; + GsdIdentityInquiryFunc inquiry_func; + gpointer inquiry_data; + GMutex inquiry_lock; + GCond inquiry_finished_condition; + volatile gboolean is_inquiring; + }; + }; +} Operation; + +typedef struct +{ + GsdKerberosIdentityManager *manager; + GsdIdentity *identity; +} IdentitySignalWork; + +static GsdIdentityManager *gsd_kerberos_identity_manager_singleton; + +static void identity_manager_interface_init (GsdIdentityManagerInterface *interface); +static void initable_interface_init (GInitableIface *interface); + +static void on_identity_expired (GsdIdentity *identity, + GsdKerberosIdentityManager *self); + +void gsd_kerberos_identity_manager_start_test (GsdKerberosIdentityManager *manager, + GError **error); +void gsd_kerberos_identity_manager_stop_test (GsdKerberosIdentityManager *manager, + GError **error); + +G_DEFINE_TYPE_WITH_CODE (GsdKerberosIdentityManager, + gsd_kerberos_identity_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GSD_TYPE_IDENTITY_MANAGER, + identity_manager_interface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_interface_init)); + +static Operation * +operation_new (GsdKerberosIdentityManager *self, + GCancellable *cancellable, + OperationType type, + GSimpleAsyncResult *result) +{ + Operation *operation; + + operation = g_slice_new (Operation); + + operation->manager = self; + operation->type = type; + + if (cancellable == NULL) { + cancellable = g_cancellable_new (); + } else { + g_object_ref (cancellable); + } + operation->cancellable = cancellable; + + if (result != NULL) { + g_object_ref (result); + } + operation->result = result; + + operation->identity = NULL; + + return operation; +} + +static void +operation_free (Operation *operation) +{ + g_clear_object (&operation->cancellable); + + if (operation->type != OPERATION_TYPE_SIGN_IN && + operation->type != OPERATION_TYPE_GET_IDENTITY) { + g_clear_object (&operation->identity); + } else { + g_clear_pointer (&operation->identifier, g_free); + } + + g_clear_object (&operation->result); + + g_slice_free (Operation, operation); +} + +static void +schedule_refresh (GsdKerberosIdentityManager *self) +{ + Operation *operation; + + g_atomic_int_inc (&self->priv->pending_refresh_count); + + operation = operation_new (self, NULL, OPERATION_TYPE_REFRESH, NULL); + g_async_queue_push (self->priv->pending_operations, operation); +} + +static IdentitySignalWork * +identity_signal_work_new (GsdKerberosIdentityManager *self, + GsdIdentity *identity) +{ + IdentitySignalWork *work; + + work = g_slice_new (IdentitySignalWork); + work->manager = self; + work->identity = g_object_ref (identity); + + return work; +} + +static void +identity_signal_work_free (IdentitySignalWork *work) +{ + g_object_unref (work->identity); + g_slice_free (IdentitySignalWork, work); +} + +static void +on_identity_expired (GsdIdentity *identity, + GsdKerberosIdentityManager *self) +{ + _gsd_identity_manager_emit_identity_expired (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +on_identity_unexpired (GsdIdentity *identity, + GsdKerberosIdentityManager *self) +{ + g_debug ("GsdKerberosIdentityManager: identity unexpired"); + /* If an identity is now unexpired, that means some sort of weird + * clock skew happened and we should just do a full refresh, since it's + * probably affected more than one identity + */ + schedule_refresh (self); +} + +static void +on_identity_expiring (GsdIdentity *identity, + GsdKerberosIdentityManager *self) +{ + g_debug ("GsdKerberosIdentityManager: identity about to expire"); + _gsd_identity_manager_emit_identity_expiring (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +on_identity_needs_renewal (GsdIdentity *identity, + GsdKerberosIdentityManager *self) +{ + g_debug ("GsdKerberosIdentityManager: identity needs renewal"); + _gsd_identity_manager_emit_identity_needs_renewal (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +on_identity_needs_refresh (GsdIdentity *identity, + GsdKerberosIdentityManager *self) +{ + g_debug ("GsdKerberosIdentityManager: needs refresh"); + schedule_refresh (self); +} + +static void +watch_for_identity_expiration (GsdKerberosIdentityManager *self, + GsdIdentity *identity) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (identity), + G_CALLBACK (on_identity_expired), + self); + g_signal_connect (G_OBJECT (identity), + "expired", + G_CALLBACK (on_identity_expired), + self); + + g_signal_handlers_disconnect_by_func (G_OBJECT (identity), + G_CALLBACK (on_identity_unexpired), + self); + g_signal_connect (G_OBJECT (identity), + "unexpired", + G_CALLBACK (on_identity_unexpired), + self); + + g_signal_handlers_disconnect_by_func (G_OBJECT (identity), + G_CALLBACK (on_identity_expiring), + self); + g_signal_connect (G_OBJECT (identity), + "expiring", + G_CALLBACK (on_identity_expiring), + self); + + g_signal_handlers_disconnect_by_func (G_OBJECT (identity), + G_CALLBACK (on_identity_needs_renewal), + self); + g_signal_connect (G_OBJECT (identity), + "needs-renewal", + G_CALLBACK (on_identity_needs_renewal), + self); + + g_signal_handlers_disconnect_by_func (G_OBJECT (identity), + G_CALLBACK (on_identity_needs_refresh), + self); + g_signal_connect (G_OBJECT (identity), + "needs-refresh", + G_CALLBACK (on_identity_needs_refresh), + self); +} + +static void +do_identity_signal_added_work (IdentitySignalWork *work) +{ + GsdKerberosIdentityManager *self = work->manager; + GsdIdentity *identity = work->identity; + + watch_for_identity_expiration (self, identity); + _gsd_identity_manager_emit_identity_added (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +do_identity_signal_removed_work (IdentitySignalWork *work) +{ + GsdKerberosIdentityManager *self = work->manager; + GsdIdentity *identity = work->identity; + + _gsd_identity_manager_emit_identity_removed (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +do_identity_signal_renamed_work (IdentitySignalWork *work) +{ + GsdKerberosIdentityManager *self = work->manager; + GsdIdentity *identity = work->identity; + + _gsd_identity_manager_emit_identity_renamed (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +do_identity_signal_refreshed_work (IdentitySignalWork *work) +{ + GsdKerberosIdentityManager *self = work->manager; + GsdIdentity *identity = work->identity; + + watch_for_identity_expiration (self, identity); + _gsd_identity_manager_emit_identity_refreshed (GSD_IDENTITY_MANAGER (self), identity); +} + +static void +remove_identity (GsdKerberosIdentityManager *self, + Operation *operation, + GsdIdentity *identity) +{ + + IdentitySignalWork *work; + const char *identifier; + char *name; + GList *other_identities = NULL; + + identifier = gsd_identity_get_identifier (identity); + name = gsd_kerberos_identity_get_realm_name (GSD_KERBEROS_IDENTITY (identity)); + + if (name != NULL) { + other_identities = g_hash_table_lookup (self->priv->identities_by_realm, + name); + g_hash_table_remove (self->priv->identities_by_realm, name); + + other_identities = g_list_remove (other_identities, identity); + } + + + if (other_identities != NULL) { + g_hash_table_replace (self->priv->identities_by_realm, + g_strdup (name), + other_identities); + } + g_free (name); + + work = identity_signal_work_new (self, identity); + g_hash_table_remove (self->priv->expired_identities, + identifier); + g_hash_table_remove (self->priv->identities, + identifier); + + g_io_scheduler_job_send_to_mainloop (operation->job, + (GSourceFunc) + do_identity_signal_removed_work, + work, + (GDestroyNotify) + identity_signal_work_free); + /* If there's only one identity for this realm now, then we can + * rename that identity to just the realm name + */ + if (other_identities != NULL && other_identities->next == NULL) { + GsdIdentity *other_identity = other_identities->data; + + work = identity_signal_work_new (self, other_identity); + + g_io_scheduler_job_send_to_mainloop (operation->job, + (GSourceFunc) + do_identity_signal_renamed_work, + work, + (GDestroyNotify) + identity_signal_work_free); + } +} + +static void +drop_stale_identities (GsdKerberosIdentityManager *self, + Operation *operation, + GHashTable *known_identities) +{ + GList *stale_identity_ids; + GList *node; + + stale_identity_ids = g_hash_table_get_keys (self->priv->identities); + + node = stale_identity_ids; + while (node != NULL) { + GsdIdentity *identity; + const char *identifier = node->data; + + identity = g_hash_table_lookup (known_identities, identifier); + if (identity == NULL) { + identity = g_hash_table_lookup (self->priv->identities, + identifier); + + if (identity != NULL) { + remove_identity (self, operation, identity); + } + } + node = node->next; + } + g_list_free (stale_identity_ids); +} + +static void +update_identity (GsdKerberosIdentityManager *self, + Operation *operation, + GsdIdentity *identity, + GsdIdentity *new_identity) +{ + + gsd_kerberos_identity_update (GSD_KERBEROS_IDENTITY (identity), + GSD_KERBEROS_IDENTITY (new_identity)); + + if (gsd_identity_is_signed_in (identity)) { + IdentitySignalWork *work; + + /* if it's not expired, send out a refresh signal */ + g_debug ("GsdKerberosIdentityManager: identity '%s' refreshed", + gsd_identity_get_identifier (identity)); + + work = identity_signal_work_new (self, identity); + g_io_scheduler_job_send_to_mainloop (operation->job, + (GSourceFunc) + do_identity_signal_refreshed_work, + work, + (GDestroyNotify) + identity_signal_work_free); + } +} + +static void +add_identity (GsdKerberosIdentityManager *self, + Operation *operation, + GsdIdentity *identity, + const char *identifier) +{ + IdentitySignalWork *work; + + g_hash_table_replace (self->priv->identities, + g_strdup (identifier), + g_object_ref (identity)); + + if (!gsd_identity_is_signed_in (identity)) { + g_hash_table_replace (self->priv->expired_identities, + g_strdup (identifier), + identity); + } + + work = identity_signal_work_new (self, identity); + g_io_scheduler_job_send_to_mainloop (operation->job, + (GSourceFunc) + do_identity_signal_added_work, + work, + (GDestroyNotify) + identity_signal_work_free); +} + +static void +refresh_identity (GsdKerberosIdentityManager *self, + Operation *operation, + GHashTable *refreshed_identities, + GsdIdentity *identity) +{ + const char *identifier; + GsdIdentity *old_identity; + + identifier = gsd_identity_get_identifier (identity); + + if (identifier == NULL) { + return; + } + old_identity = g_hash_table_lookup (self->priv->identities, identifier); + + if (old_identity != NULL) { + g_debug ("GsdKerberosIdentityManager: refreshing identity '%s'", identifier); + update_identity (self, operation, old_identity, identity); + + /* Reuse the old identity, so any object data set up on it doesn't + * disappear spurriously + */ + identifier = gsd_identity_get_identifier (old_identity); + identity = old_identity; + } else { + g_debug ("GsdKerberosIdentityManager: adding new identity '%s'", identifier); + add_identity (self, operation, identity, identifier); + } + + /* Track refreshed identities so we can emit removals when we're done fully + * enumerating the collection of credential caches + */ + g_hash_table_replace (refreshed_identities, + g_strdup (identifier), + g_object_ref (identity)); +} + +static gboolean +refresh_identities (GsdKerberosIdentityManager *self, + Operation *operation) +{ + krb5_error_code error_code; + krb5_ccache cache; + krb5_cccol_cursor cursor; + const char *error_message; + GHashTable *refreshed_identities; + + /* If we have more refreshes queued up, don't bother doing this one + */ + if (!g_atomic_int_dec_and_test (&self->priv->pending_refresh_count)) { + return FALSE; + } + + g_debug ("GsdKerberosIdentityManager: Refreshing identities"); + refreshed_identities = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) + g_free, + (GDestroyNotify) + g_object_unref); + error_code = krb5_cccol_cursor_new (self->priv->kerberos_context, &cursor); + + if (error_code != 0) { + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentityManager: Error looking up available credential caches: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + goto done; + } + + error_code = krb5_cccol_cursor_next (self->priv->kerberos_context, + cursor, + &cache); + + while (error_code == 0 && cache != NULL) { + GsdIdentity *identity; + + identity = gsd_kerberos_identity_new (self->priv->kerberos_context, + cache); + + if (identity != NULL) { + refresh_identity (self, operation, refreshed_identities, identity); + } + + krb5_cc_close (self->priv->kerberos_context, cache); + error_code = krb5_cccol_cursor_next (self->priv->kerberos_context, + cursor, + &cache); + } + + if (error_code != 0) { + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentityManager: Error iterating over available credential caches: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + } + + krb5_cccol_cursor_free (self->priv->kerberos_context, &cursor); +done: + drop_stale_identities (self, operation, refreshed_identities); + g_hash_table_unref (refreshed_identities); + + return TRUE; +} + +static int +identity_sort_func (GsdIdentity *a, + GsdIdentity *b) +{ + return g_strcmp0 (gsd_identity_get_identifier (a), + gsd_identity_get_identifier (b)); +} + +static void +free_identity_list (GList *list) +{ + g_list_foreach (list, (GFunc) g_object_unref, NULL); + g_list_free (list); +} + +static void +list_identities (GsdKerberosIdentityManager *self, + Operation *operation) +{ + GList *identities; + + g_debug ("GsdKerberosIdentityManager: Listing identities"); + identities = g_hash_table_get_values (self->priv->identities); + + identities = g_list_sort (identities, + (GCompareFunc) + identity_sort_func); + + g_list_foreach (identities, (GFunc) g_object_ref, NULL); + g_simple_async_result_set_op_res_gpointer (operation->result, + identities, + (GDestroyNotify) + free_identity_list); +} + +static void +renew_identity (GsdKerberosIdentityManager *self, + Operation *operation) +{ + GError *error; + gboolean was_renewed; + char *identity_name; + + identity_name = gsd_kerberos_identity_get_principal_name (GSD_KERBEROS_IDENTITY (operation->identity)); + g_debug ("GsdKerberosIdentityManager: renewing identity %s", identity_name); + g_free (identity_name); + + error = NULL; + was_renewed = gsd_kerberos_identity_renew (GSD_KERBEROS_IDENTITY (operation->identity), + &error); + + if (!was_renewed) { + g_debug ("GsdKerberosIdentityManager: could not renew identity: %s", + error->message); + + g_simple_async_result_set_from_error (operation->result, + error); + } + + g_simple_async_result_set_op_res_gboolean (operation->result, + was_renewed); +} + +static void +do_identity_inquiry (Operation *operation) +{ + if (operation->inquiry_func == NULL) { + return; + } + + operation->inquiry_func (operation->inquiry, + operation->cancellable, + operation->inquiry_data); +} + +static void +stop_waiting_on_inquiry (Operation *operation) +{ + g_mutex_lock (&operation->inquiry_lock); + if (operation->is_inquiring) { + operation->is_inquiring = FALSE; + g_cond_signal (&operation->inquiry_finished_condition); + } + g_mutex_unlock (&operation->inquiry_lock); +} + +static void +on_kerberos_identity_inquiry_complete (GsdIdentityInquiry *inquiry, + Operation *operation) +{ + stop_waiting_on_inquiry (operation); +} + +static void +start_inquiry (Operation *operation, + GsdIdentityInquiry *inquiry) +{ + operation->is_inquiring = TRUE; + + g_signal_connect (G_OBJECT (inquiry), + "complete", + G_CALLBACK (on_kerberos_identity_inquiry_complete), + operation); + + operation->inquiry = inquiry; + g_io_scheduler_job_send_to_mainloop (operation->job, + (GSourceFunc) + do_identity_inquiry, + operation, + (GDestroyNotify) + NULL); +} + +static void +wait_for_inquiry_to_complete (Operation *operation, + GsdKerberosIdentityInquiry *inquiry) +{ + g_mutex_lock (&operation->inquiry_lock); + while (operation->is_inquiring) { + g_cond_wait (&operation->inquiry_finished_condition, + &operation->inquiry_lock); + } + g_mutex_unlock (&operation->inquiry_lock); +} + +static void +on_sign_in_operation_cancelled (GCancellable *cancellable, + Operation *operation) +{ + stop_waiting_on_inquiry (operation); +} + +static void +on_kerberos_identity_inquiry (GsdKerberosIdentityInquiry *inquiry, + GCancellable *cancellable, + Operation *operation) +{ + gulong handler_id; + + start_inquiry (operation, GSD_IDENTITY_INQUIRY (inquiry)); + + handler_id = g_cancellable_connect (cancellable, + G_CALLBACK (on_sign_in_operation_cancelled), + operation, + NULL); + + if ((operation->cancellable == NULL) || + !g_cancellable_is_cancelled (operation->cancellable)) { + wait_for_inquiry_to_complete (operation, inquiry); + } + + g_cancellable_disconnect (cancellable, handler_id); +} +static void +get_identity (GsdKerberosIdentityManager *self, + Operation *operation) +{ + GsdIdentity *identity; + GError *error; + + g_debug ("GsdKerberosIdentityManager: get identity %s", operation->identifier); + identity = g_hash_table_lookup (self->priv->identities, + operation->identifier); + + if (identity == NULL) { + g_simple_async_result_set_error (operation->result, + GSD_IDENTITY_MANAGER_ERROR, + GSD_IDENTITY_MANAGER_ERROR_IDENTITY_NOT_FOUND, + _("Could not find identity")); + g_simple_async_result_set_op_res_gpointer (operation->result, + NULL, NULL); + + return; + } + + g_simple_async_result_set_op_res_gpointer (operation->result, + g_object_ref (identity), + (GDestroyNotify) + g_object_unref); +} + +static void +sign_in_identity (GsdKerberosIdentityManager *self, + Operation *operation) +{ + GsdIdentity *identity; + GError *error; + krb5_error_code error_code; + + g_debug ("GsdKerberosIdentityManager: signing in identity %s", operation->identifier); + identity = g_hash_table_lookup (self->priv->identities, + operation->identifier); + if (identity == NULL) { + krb5_ccache credentials_cache; + error_code = krb5_cc_new_unique (self->priv->kerberos_context, + self->priv->credentials_cache_type, + NULL, + &credentials_cache); + + if (error_code != 0) { + const char *error_message; + + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentityManager: Error creating new cache for identity credentials: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + } else { + identity = gsd_kerberos_identity_new (self->priv->kerberos_context, + credentials_cache); + if (identity == NULL) { + krb5_cc_close (self->priv->kerberos_context, credentials_cache); + } + } + } + + if (identity == NULL) { + g_simple_async_result_set_error (operation->result, + GSD_IDENTITY_MANAGER_ERROR, + GSD_IDENTITY_MANAGER_ERROR_CREATING_IDENTITY, + _("Could not create identity")); + g_simple_async_result_set_op_res_gpointer (operation->result, + NULL, NULL); + + return; + } + + g_hash_table_replace (self->priv->identities, + g_strdup (operation->identifier), + g_object_ref (identity)); + + error = NULL; + if (!gsd_kerberos_identity_sign_in (GSD_KERBEROS_IDENTITY (identity), + operation->identifier, + (GsdIdentityInquiryFunc) + on_kerberos_identity_inquiry, + operation, + NULL, + operation->cancellable, + &error)) { + g_simple_async_result_set_from_error (operation->result, + error); + g_simple_async_result_set_op_res_gpointer (operation->result, + NULL, NULL); + + } else { + g_simple_async_result_set_op_res_gpointer (operation->result, + g_object_ref (identity), + (GDestroyNotify) + g_object_unref); + } +} + +static void +sign_out_identity (GsdKerberosIdentityManager *self, + Operation *operation) +{ + GError *error; + gboolean was_signed_out; + char *identity_name; + + identity_name = gsd_kerberos_identity_get_principal_name (GSD_KERBEROS_IDENTITY (operation->identity)); + g_debug ("GsdKerberosIdentityManager: signing out identity %s", identity_name); + g_free (identity_name); + + error = NULL; + was_signed_out = gsd_kerberos_identity_erase (GSD_KERBEROS_IDENTITY (operation->identity), + &error); + + if (!was_signed_out) { + g_debug ("GsdKerberosIdentityManager: could not sign out identity: %s", + error->message); + g_error_free (error); + } +} + +static void +block_scheduler_job (GsdKerberosIdentityManager *self) +{ + g_mutex_lock (&self->priv->scheduler_job_lock); + while (self->priv->is_blocking_scheduler_job) { + g_cond_wait (&self->priv->scheduler_job_unblocked, &self->priv->scheduler_job_lock); + } + self->priv->is_blocking_scheduler_job = TRUE; + g_mutex_unlock (&self->priv->scheduler_job_lock); +} + +static void +stop_blocking_scheduler_job (GsdKerberosIdentityManager *self) +{ + g_mutex_lock (&self->priv->scheduler_job_lock); + self->priv->is_blocking_scheduler_job = FALSE; + g_cond_signal (&self->priv->scheduler_job_unblocked); + g_mutex_unlock (&self->priv->scheduler_job_lock); +} + +static void +wait_for_scheduler_job_to_become_unblocked (GsdKerberosIdentityManager *self) +{ + g_mutex_lock (&self->priv->scheduler_job_lock); + while (self->priv->is_blocking_scheduler_job) { + g_cond_wait (&self->priv->scheduler_job_unblocked, &self->priv->scheduler_job_lock); + } + g_mutex_unlock (&self->priv->scheduler_job_lock); +} + +static void +on_job_cancelled (GCancellable *cancellable, + GsdKerberosIdentityManager *self) +{ + Operation *operation; + operation = operation_new (self, + cancellable, + OPERATION_TYPE_STOP_JOB, + NULL); + g_async_queue_push (self->priv->pending_operations, operation); + + stop_blocking_scheduler_job (self); +} + +static gboolean +on_job_scheduled (GIOSchedulerJob *job, + GCancellable *cancellable, + GsdKerberosIdentityManager *self) +{ + GAsyncQueue *pending_operations; + + g_assert (cancellable != NULL); + + g_cancellable_connect (cancellable, + G_CALLBACK (on_job_cancelled), + self, + NULL); + + /* Take ownership of queue, since we may out live the identity manager */ + pending_operations = g_async_queue_ref (self->priv->pending_operations); + while (!g_cancellable_is_cancelled (cancellable)) { + Operation *operation; + gboolean processed_operation; + GError *error = NULL; + + operation = g_async_queue_pop (pending_operations); + + if (operation->result != NULL && + g_cancellable_set_error_if_cancelled (operation->cancellable, &error)) { + g_simple_async_result_take_error (operation->result, + error); + g_simple_async_result_complete_in_idle (operation->result); + g_object_unref (operation->result); + operation->result = NULL; + continue; + } + + operation->job = job; + + switch (operation->type) { + case OPERATION_TYPE_STOP_JOB: + /* do nothing, loop will exit next iteration since cancellable + * is cancelled + */ + g_assert (g_cancellable_is_cancelled (cancellable)); + operation_free (operation); + continue; + case OPERATION_TYPE_REFRESH: + processed_operation = refresh_identities (operation->manager, operation); + break; + case OPERATION_TYPE_GET_IDENTITY: + get_identity (operation->manager, operation); + processed_operation = TRUE; + break; + case OPERATION_TYPE_LIST: + list_identities (operation->manager, operation); + processed_operation = TRUE; + + /* We want to block refreshes (and their associated "added" + * and "removed" signals) until the caller has had + * a chance to look at the batch of + * results we already processed + */ + g_assert (operation->result != NULL); + + g_debug ("GsdKerberosIdentityManager: Blocking until identities list processed"); + block_scheduler_job (self); + g_object_weak_ref (G_OBJECT (operation->result), + (GWeakNotify) + stop_blocking_scheduler_job, + self); + g_debug ("GsdKerberosIdentityManager: Continuing"); + break; + case OPERATION_TYPE_SIGN_IN: + sign_in_identity (operation->manager, operation); + processed_operation = TRUE; + break; + case OPERATION_TYPE_SIGN_OUT: + sign_out_identity (operation->manager, operation); + processed_operation = TRUE; + break; + case OPERATION_TYPE_RENEW: + renew_identity (operation->manager, operation); + processed_operation = TRUE; + break; + } + + operation->job = NULL; + + if (operation->result != NULL) { + g_simple_async_result_complete_in_idle (operation->result); + g_object_unref (operation->result); + operation->result = NULL; + } + operation_free (operation); + + wait_for_scheduler_job_to_become_unblocked (self); + + /* Don't bother saying "Waiting for next operation" if this operation + * was a no-op, since the debug spew probably already says the message + */ + if (processed_operation) { + g_debug ("GsdKerberosIdentityManager: Waiting for next operation"); + } + } + + g_async_queue_unref (pending_operations); + + return FALSE; +} + +static void +gsd_kerberos_identity_manager_get_identity (GsdIdentityManager *manager, + const char *identifier, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + GSimpleAsyncResult *result; + Operation *operation; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + gsd_kerberos_identity_manager_get_identity); + operation = operation_new (self, + cancellable, + OPERATION_TYPE_GET_IDENTITY, + result); + g_object_unref (result); + + operation->identifier = g_strdup (identifier); + + g_async_queue_push (self->priv->pending_operations, operation); +} + +static GsdIdentity * +gsd_kerberos_identity_manager_get_identity_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + GsdIdentity *identity; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) { + return NULL; + } + + identity = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + + return identity; +} + +static void +gsd_kerberos_identity_manager_list_identities (GsdIdentityManager *manager, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + GSimpleAsyncResult *result; + Operation *operation; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + gsd_kerberos_identity_manager_list_identities); + + operation = operation_new (self, + cancellable, + OPERATION_TYPE_LIST, + result); + g_object_unref (result); + + g_async_queue_push (self->priv->pending_operations, operation); +} + +static GList * +gsd_kerberos_identity_manager_list_identities_finish (GsdIdentityManager *manager, + GAsyncResult *result, + GError **error) +{ + GList *identities; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) { + return NULL; + } + + identities = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + + return identities; + +} + +static void +gsd_kerberos_identity_manager_renew_identity (GsdIdentityManager *manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + GSimpleAsyncResult *result; + Operation *operation; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + gsd_kerberos_identity_manager_renew_identity); + operation = operation_new (self, + cancellable, + OPERATION_TYPE_RENEW, + result); + g_object_unref (result); + + operation->identity = g_object_ref (identity); + + g_async_queue_push (self->priv->pending_operations, operation); +} + +static void +gsd_kerberos_identity_manager_renew_identity_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) { + return; + } + + return; +} + +static void +gsd_kerberos_identity_manager_sign_identity_in (GsdIdentityManager *manager, + const char *identifier, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + GSimpleAsyncResult *result; + Operation *operation; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + gsd_kerberos_identity_manager_sign_identity_in); + operation = operation_new (self, + cancellable, + OPERATION_TYPE_SIGN_IN, + result); + g_object_unref (result); + + operation->identifier = g_strdup (identifier); + operation->inquiry_func = inquiry_func; + operation->inquiry_data = inquiry_data; + g_mutex_init (&operation->inquiry_lock); + g_cond_init (&operation->inquiry_finished_condition); + operation->is_inquiring = FALSE; + + g_async_queue_push (self->priv->pending_operations, operation); +} + +static GsdIdentity * +gsd_kerberos_identity_manager_sign_identity_in_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + GsdIdentity *identity; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) { + return NULL; + } + + identity = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + + return identity; +} + +static void +gsd_kerberos_identity_manager_sign_identity_out (GsdIdentityManager *manager, + GsdIdentity *identity, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + GSimpleAsyncResult *result; + Operation *operation; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + gsd_kerberos_identity_manager_sign_identity_out); + operation = operation_new (self, + cancellable, + OPERATION_TYPE_SIGN_OUT, + result); + g_object_unref (result); + + operation->identity = g_object_ref (identity); + + g_async_queue_push (self->priv->pending_operations, operation); +} + +static void +gsd_kerberos_identity_manager_sign_identity_out_finish (GsdIdentityManager *self, + GAsyncResult *result, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) { + return; + } + + return; +} + +static char * +gsd_kerberos_identity_manager_name_identity (GsdIdentityManager *manager, + GsdIdentity *identity) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (manager); + char *name; + GList *other_identities; + gboolean other_identity_needs_rename; + + name = gsd_kerberos_identity_get_realm_name (GSD_KERBEROS_IDENTITY (identity)); + + if (name == NULL) { + return NULL; + } + + other_identities = g_hash_table_lookup (self->priv->identities_by_realm, + name); + + /* If there was already exactly one identity for this realm before, + * then it was going by just the realm name, so we need to rename it + * to use the full principle name + */ + if (other_identities != NULL && + other_identities->next == NULL && + other_identities->data != identity) { + other_identity_needs_rename = TRUE; + } + + other_identities = g_list_remove (other_identities, identity); + other_identities = g_list_prepend (other_identities, identity); + + g_hash_table_replace (self->priv->identities_by_realm, + g_strdup (name), + other_identities); + + if (other_identities->next != NULL) { + g_free (name); + name = gsd_kerberos_identity_get_principal_name (GSD_KERBEROS_IDENTITY (identity)); + if (other_identity_needs_rename) { + GsdIdentity *other_identity = other_identities->next->data; + + _gsd_identity_manager_emit_identity_renamed (GSD_IDENTITY_MANAGER (self), + other_identity); + } + } + + return name; +} + +static void +identity_manager_interface_init (GsdIdentityManagerInterface *interface) +{ + interface->get_identity = gsd_kerberos_identity_manager_get_identity; + interface->get_identity_finish = gsd_kerberos_identity_manager_get_identity_finish; + interface->list_identities = gsd_kerberos_identity_manager_list_identities; + interface->list_identities_finish = gsd_kerberos_identity_manager_list_identities_finish; + interface->sign_identity_in = gsd_kerberos_identity_manager_sign_identity_in; + interface->sign_identity_in_finish = gsd_kerberos_identity_manager_sign_identity_in_finish; + interface->sign_identity_out = gsd_kerberos_identity_manager_sign_identity_out; + interface->sign_identity_out_finish = gsd_kerberos_identity_manager_sign_identity_out_finish; + interface->renew_identity = gsd_kerberos_identity_manager_renew_identity; + interface->renew_identity_finish = gsd_kerberos_identity_manager_renew_identity_finish; + interface->name_identity = gsd_kerberos_identity_manager_name_identity; +} + +static void +on_credentials_cache_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent *event_type, + GsdKerberosIdentityManager *self) +{ + schedule_refresh (self); +} + +static gboolean +monitor_credentials_cache (GsdKerberosIdentityManager *self, + GError **error) +{ + krb5_ccache default_cache; + const char *cache_type; + const char *cache_path; + GFile *file; + GFileMonitor *monitor; + krb5_error_code error_code; + GError *monitoring_error; + + error_code = krb5_cc_default (self->priv->kerberos_context, + &default_cache); + + if (error_code != 0) { + const char *error_message; + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + + g_set_error_literal (error, + GSD_IDENTITY_MANAGER_ERROR, + GSD_IDENTITY_MANAGER_ERROR_ACCESSING_CREDENTIALS, + error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + + return FALSE; + } + + cache_type = krb5_cc_get_type (self->priv->kerberos_context, + default_cache); + g_assert (cache_type != NULL); + + if (strcmp (cache_type, "FILE") != 0 && + strcmp (cache_type, "DIR") != 0) { + g_set_error (error, + GSD_IDENTITY_MANAGER_ERROR, + GSD_IDENTITY_MANAGER_ERROR_UNSUPPORTED_CREDENTIALS, + "Only 'FILE' and 'DIR' credential cache types are really supported, not '%s'", + cache_type); + return FALSE; + } + + g_free (self->priv->credentials_cache_type); + self->priv->credentials_cache_type = g_strdup (cache_type); + + /* If we're using a FILE type credential cache, then the + * default cache file is the only cache we care about, + * and its path is what we want to monitor. + * + * If we're using a DIR type credential cache, then the default + * cache file is one of many possible cache files, all in the + * same directory. We want to monitor that directory. + */ + cache_path = krb5_cc_get_name (self->priv->kerberos_context, + default_cache); + + /* The cache name might have a : in front of it. + * FIXME: figure out if that behavior is by design, or some + * odd bug. + */ + if (cache_path[0] == ':') { + cache_path++; + } + + file = g_file_new_for_path (cache_path); + + monitoring_error = NULL; + if (strcmp (cache_type, "FILE") == 0) { + monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &monitoring_error); + } else if (strcmp (cache_type, "DIR") == 0) { + GFile *directory; + + directory = g_file_get_parent (file); + monitor = g_file_monitor_directory (directory, + G_FILE_MONITOR_NONE, + NULL, + &monitoring_error); + g_object_unref (directory); + + } else { + g_assert_not_reached (); + } + g_object_unref (file); + + if (monitor == NULL) { + g_propagate_error (error, monitoring_error); + return FALSE; + } + + self->priv->credentials_cache_changed_signal_id = g_signal_connect (G_OBJECT (monitor), + "changed", + G_CALLBACK (on_credentials_cache_changed), + self); + self->priv->credentials_cache_monitor = monitor; + + return TRUE; +} + +static void +stop_watching_credentials_cache (GsdKerberosIdentityManager *self) +{ + if (!g_file_monitor_is_cancelled (self->priv->credentials_cache_monitor)) { + g_file_monitor_cancel (self->priv->credentials_cache_monitor); + } + g_object_unref (self->priv->credentials_cache_monitor); + self->priv->credentials_cache_monitor = NULL; +} + +static gboolean +gsd_kerberos_identity_manager_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (initable); + krb5_error_code error_code; + GError *monitoring_error; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + error_code = krb5_init_context (&self->priv->kerberos_context); + + if (error_code != 0) { + const char *error_message; + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + + g_set_error_literal (error, + GSD_IDENTITY_MANAGER_ERROR, + GSD_IDENTITY_MANAGER_ERROR_INITIALIZING, + error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + + return FALSE; + } + + monitoring_error = NULL; + if (!monitor_credentials_cache (self, &monitoring_error)) { + g_warning ("GsdKerberosIdentityManager: Could not monitor credentials: %s", + monitoring_error->message); + g_error_free (monitoring_error); + } + + schedule_refresh (self); + + return TRUE; +} + +static void +initable_interface_init (GInitableIface *interface) +{ + interface->init = gsd_kerberos_identity_manager_initable_init; +} + +static void +gsd_kerberos_identity_manager_init (GsdKerberosIdentityManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_KERBEROS_IDENTITY_MANAGER, + GsdKerberosIdentityManagerPrivate); + self->priv->identities = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) + g_free, + (GDestroyNotify) + g_object_unref); + self->priv->expired_identities = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) + g_free, + NULL); + + self->priv->identities_by_realm = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) + g_free, + NULL); + self->priv->pending_operations = g_async_queue_new (); + + g_mutex_init (&self->priv->scheduler_job_lock); + g_cond_init (&self->priv->scheduler_job_unblocked); + + self->priv->scheduler_cancellable = g_cancellable_new (); + g_io_scheduler_push_job ((GIOSchedulerJobFunc) + on_job_scheduled, + self, + NULL, + G_PRIORITY_DEFAULT, + self->priv->scheduler_cancellable); + +} + +static void +cancel_pending_operations (GsdKerberosIdentityManager *self) +{ + Operation *operation; + + operation = g_async_queue_try_pop (self->priv->pending_operations); + while (operation != NULL) { + if (!g_cancellable_is_cancelled (operation->cancellable)) { + g_cancellable_cancel (operation->cancellable); + } + operation_free (operation); + operation = g_async_queue_try_pop (self->priv->pending_operations); + } +} + +static void +gsd_kerberos_identity_manager_dispose (GObject *object) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (object); + + if (self->priv->identities_by_realm != NULL) { + g_hash_table_unref (self->priv->identities_by_realm); + self->priv->identities_by_realm = NULL; + } + + if (self->priv->expired_identities != NULL) { + g_hash_table_unref (self->priv->expired_identities); + self->priv->expired_identities = NULL; + } + + if (self->priv->identities != NULL) { + g_hash_table_unref (self->priv->identities); + self->priv->identities = NULL; + } + + if (self->priv->credentials_cache_monitor != NULL) { + stop_watching_credentials_cache (self); + } + + if (self->priv->pending_operations != NULL) { + cancel_pending_operations (self); + } + + if (self->priv->scheduler_cancellable != NULL) { + if (!g_cancellable_is_cancelled (self->priv->scheduler_cancellable)) { + g_cancellable_cancel (self->priv->scheduler_cancellable); + } + + g_clear_object (&self->priv->scheduler_cancellable); + } + + /* Note, other thread may still be holding a local reference to queue + * while it shuts down from cancelled scheduler_cancellable above + */ + if (self->priv->pending_operations != NULL) { + g_async_queue_unref (self->priv->pending_operations); + self->priv->pending_operations = NULL; + } + + G_OBJECT_CLASS (gsd_kerberos_identity_manager_parent_class)->dispose (object); +} + +static void +gsd_kerberos_identity_manager_finalize (GObject *object) +{ + GsdKerberosIdentityManager *self = GSD_KERBEROS_IDENTITY_MANAGER (object); + + g_free (self->priv->credentials_cache_type); + + g_cond_clear (&self->priv->scheduler_job_unblocked); + krb5_free_context (self->priv->kerberos_context); + + G_OBJECT_CLASS (gsd_kerberos_identity_manager_parent_class)->finalize (object); +} + +static void +gsd_kerberos_identity_manager_class_init (GsdKerberosIdentityManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsd_kerberos_identity_manager_dispose; + object_class->finalize = gsd_kerberos_identity_manager_finalize; + + g_type_class_add_private (klass, sizeof (GsdKerberosIdentityManagerPrivate)); +} + +GsdIdentityManager * +gsd_kerberos_identity_manager_new (GCancellable *cancellable, + GError **error) +{ + if (gsd_kerberos_identity_manager_singleton == NULL) { + GObject *object; + + object = g_object_new (GSD_TYPE_KERBEROS_IDENTITY_MANAGER, NULL); + + gsd_kerberos_identity_manager_singleton = GSD_IDENTITY_MANAGER (object); + g_object_add_weak_pointer (object, (gpointer *) &gsd_kerberos_identity_manager_singleton); + + error = NULL; + if (!g_initable_init (G_INITABLE (object), cancellable, error)) { + g_object_unref (object); + return NULL; + } + + } else { + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return NULL; + } + g_object_ref (gsd_kerberos_identity_manager_singleton); + } + + return gsd_kerberos_identity_manager_singleton; +} diff --git a/plugins/identity/gsd-kerberos-identity-manager.h b/plugins/identity/gsd-kerberos-identity-manager.h new file mode 100644 index 00000000..43a84ba4 --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity-manager.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_KERBEROS_IDENTITY_MANAGER_H__ +#define __GSD_KERBEROS_IDENTITY_MANAGER_H__ + +#include +#include +#include + +#include "gsd-identity-manager.h" +#include "gsd-kerberos-identity.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_KERBEROS_IDENTITY_MANAGER (gsd_kerberos_identity_manager_get_type ()) +#define GSD_KERBEROS_IDENTITY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GSD_TYPE_KERBEROS_IDENTITY_MANAGER, GsdKerberosIdentityManager)) +#define GSD_KERBEROS_IDENTITY_MANAGER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GSD_TYPE_KERBEROS_IDENTITY_MANAGER, GsdKerberosIdentityManagerClass)) +#define GSD_IS_KERBEROS_IDENTITY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GSD_TYPE_KERBEROS_IDENTITY_MANAGER)) +#define GSD_IS_KERBEROS_IDENTITY_MANAGER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GSD_TYPE_KERBEROS_IDENTITY_MANAGER)) +#define GSD_KERBEROS_IDENTITY_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSD_TYPE_KERBEROS_IDENTITY_MANAGER, GsdKerberosIdentityManagerClass)) + +typedef struct _GsdKerberosIdentityManager GsdKerberosIdentityManager; +typedef struct _GsdKerberosIdentityManagerClass GsdKerberosIdentityManagerClass; +typedef struct _GsdKerberosIdentityManagerPrivate GsdKerberosIdentityManagerPrivate; struct _GsdKerberosIdentityManager +{ + GObject parent_instance; + GsdKerberosIdentityManagerPrivate *priv; +}; + +struct _GsdKerberosIdentityManagerClass +{ + GObjectClass parent_class; +}; + +GType gsd_kerberos_identity_manager_get_type (void); +GsdIdentityManager* gsd_kerberos_identity_manager_new (GCancellable *cancellable, + GError **error); + +void gsd_kerberos_identity_manager_start_test (GsdKerberosIdentityManager *manager, + GError **error); +G_END_DECLS + +#endif /* __GSD_KERBEROS_IDENTITY_MANAGER_H__ */ diff --git a/plugins/identity/gsd-kerberos-identity.c b/plugins/identity/gsd-kerberos-identity.c new file mode 100644 index 00000000..ac159117 --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity.c @@ -0,0 +1,1475 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * 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. + * + * Author: Ray Strode + */ + +#include "config.h" + +#include "gsd-identity.h" +#include "gsd-kerberos-identity.h" +#include "gsd-alarm.h" + +#include +#include +#include + +typedef enum +{ + VERIFICATION_LEVEL_UNVERIFIED, + VERIFICATION_LEVEL_ERROR, + VERIFICATION_LEVEL_EXISTS, + VERIFICATION_LEVEL_SIGNED_IN +} VerificationLevel; + +struct _GsdKerberosIdentityPrivate +{ + krb5_context kerberos_context; + krb5_ccache credentials_cache; + + char *identifier; + guint identifier_idle_id; + + char **identifier_components; + + krb5_timestamp expiration_time; + guint expiration_time_idle_id; + + GsdAlarm *expiration_alarm; + GCancellable *expiration_alarm_cancellable; + + GsdAlarm *expiring_alarm; + GCancellable *expiring_alarm_cancellable; + + GsdAlarm *renewal_alarm; + GCancellable *renewal_alarm_cancellable; + + VerificationLevel cached_verification_level; + guint is_signed_in_idle_id; +}; + +enum { + EXPIRING, + EXPIRED, + UNEXPIRED, + NEEDS_RENEWAL, + NEEDS_REFRESH, + NUMBER_OF_SIGNALS, +}; + +enum { + PROP_0, + PROP_IDENTIFIER, + PROP_IS_SIGNED_IN, + PROP_EXPIRATION_TIMESTAMP +}; + +static guint signals[NUMBER_OF_SIGNALS] = { 0 }; + +static void identity_interface_init (GsdIdentityInterface *interface); +static void initable_interface_init (GInitableIface *interface); +static void reset_alarms (GsdKerberosIdentity *self); +static void clear_alarms (GsdKerberosIdentity *self); +static gboolean gsd_kerberos_identity_is_signed_in (GsdIdentity *identity); +static void set_error_from_krb5_error_code (GsdKerberosIdentity *self, + GError **error, + gint code, + krb5_error_code error_code, + const char *format, + ...); + +G_LOCK_DEFINE_STATIC (identity_lock); + +G_DEFINE_TYPE_WITH_CODE (GsdKerberosIdentity, + gsd_kerberos_identity, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_interface_init) + G_IMPLEMENT_INTERFACE (GSD_TYPE_IDENTITY, + identity_interface_init)); +static void +gsd_kerberos_identity_dispose (GObject *object) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (object); + + G_LOCK (identity_lock); + clear_alarms (self); + + g_clear_object (&self->priv->renewal_alarm); + g_clear_object (&self->priv->expiring_alarm); + g_clear_object (&self->priv->expiration_alarm); + G_UNLOCK (identity_lock); + + if (self->priv->expiration_time_idle_id != 0) { + g_source_remove (self->priv->expiration_time_idle_id); + } + + G_OBJECT_CLASS (gsd_kerberos_identity_parent_class)->dispose (object); + +} + +static void +gsd_kerberos_identity_finalize (GObject *object) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (object); + + g_free (self->priv->identifier); + + if (self->priv->credentials_cache != NULL) { + krb5_cc_close (self->priv->kerberos_context, + self->priv->credentials_cache); + } + + G_OBJECT_CLASS (gsd_kerberos_identity_parent_class)->finalize (object); +} + +static void +gsd_kerberos_identity_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (object); + + switch (property_id) { + case PROP_IDENTIFIER: + G_LOCK (identity_lock); + g_value_set_string (value, + self->priv->identifier); + G_UNLOCK (identity_lock); + break; + case PROP_IS_SIGNED_IN: + g_value_set_boolean (value, + gsd_kerberos_identity_is_signed_in (GSD_IDENTITY (self))); + break; + case PROP_EXPIRATION_TIMESTAMP: + G_LOCK (identity_lock); + g_value_set_int64 (value, + (gint64) self->priv->expiration_time); + G_UNLOCK (identity_lock); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, + property_id, + param_spec); + break; + } +} + +static void +gsd_kerberos_identity_class_init (GsdKerberosIdentityClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsd_kerberos_identity_dispose; + object_class->finalize = gsd_kerberos_identity_finalize; + object_class->get_property = gsd_kerberos_identity_get_property; + + g_type_class_add_private (klass, sizeof (GsdKerberosIdentityPrivate)); + + signals[EXPIRING] = g_signal_new ("expiring", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[EXPIRED] = g_signal_new ("expired", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[UNEXPIRED] = g_signal_new ("unexpired", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[NEEDS_RENEWAL] = g_signal_new ("needs-renewal", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[NEEDS_REFRESH] = g_signal_new ("needs-refresh", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + g_object_class_override_property (object_class, + PROP_IDENTIFIER, + "identifier"); + g_object_class_override_property (object_class, + PROP_IS_SIGNED_IN, + "is-signed-in"); + g_object_class_override_property (object_class, + PROP_EXPIRATION_TIMESTAMP, + "expiration-timestamp"); + +} + +static char * +get_identifier (GsdKerberosIdentity *self, + GError **error) +{ + krb5_principal principal; + krb5_error_code error_code; + char *unparsed_name; + char *identifier; + + if (self->priv->credentials_cache == NULL) { + return NULL; + } + + error_code = krb5_cc_get_principal (self->priv->kerberos_context, + self->priv->credentials_cache, + &principal); + + if (error_code != 0) { + if (error_code == KRB5_CC_END) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE, + error_code, + _("Could not find identity in credential cache: %k")); + } else { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_ENUMERATING_CREDENTIALS, + error_code, + _("Could not find identity in credential cache: %k")); + } + return NULL; + } + + error_code = krb5_unparse_name_flags (self->priv->kerberos_context, + principal, + 0, + &unparsed_name); + + if (error_code != 0) { + const char *error_message; + + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error parsing principal identity name: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return NULL; + } + + identifier = g_strdup (unparsed_name); + krb5_free_unparsed_name (self->priv->kerberos_context, unparsed_name); + + return identifier; +} + +static char ** +get_identifier_components (GsdKerberosIdentity *self) +{ + krb5_principal principal; + krb5_error_code error_code; + int size, i; + GPtrArray *array; + + if (self->priv->identifier == NULL) { + return g_new0 (char *, 1); + } + + error_code = krb5_parse_name (self->priv->kerberos_context, + self->priv->identifier, + &principal); + + if (error_code != 0) { + const char *error_message; + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error looking up principal identity in credential cache: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return g_new0 (char *, 1); + } + + array = g_ptr_array_new (); + + g_ptr_array_add (array, + g_strndup (principal->realm.data, + principal->realm.length)); + + size = krb5_princ_size (self->priv->kerberos_context, + principal); + + for (i = 0; i < size; i++) { + krb5_data *component; + char *component_string; + + component = krb5_princ_component (self->priv->kerberos_context, + principal, + i); + + component_string = g_strndup (component->data, component->length); + g_ptr_array_add (array, component_string); + + } + g_ptr_array_add (array, NULL); + + return (char **) g_ptr_array_free (array, FALSE); +} + +static void +gsd_kerberos_identity_init (GsdKerberosIdentity *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GSD_TYPE_KERBEROS_IDENTITY, + GsdKerberosIdentityPrivate); + self->priv->expiration_alarm = gsd_alarm_new (); + self->priv->expiring_alarm = gsd_alarm_new (); + self->priv->renewal_alarm = gsd_alarm_new (); +} + +static void +set_error_from_krb5_error_code (GsdKerberosIdentity *self, + GError **error, + gint code, + krb5_error_code error_code, + const char *format, + ...) +{ + const char *error_message; + char *literal_message; + char *expanded_format; + va_list args; + char **chunks; + + error_message = krb5_get_error_message (self->priv->kerberos_context, + error_code); + chunks = g_strsplit (format, "%k", -1); + expanded_format = g_strjoinv (error_message , chunks); + g_strfreev (chunks); + krb5_free_error_message (self->priv->kerberos_context, error_message); + + va_start (args, format); + literal_message = g_strdup_vprintf (expanded_format, args); + va_end (args); + + g_set_error_literal (error, + GSD_IDENTITY_ERROR, + code, + literal_message); + g_free (literal_message); +} + +char * +gsd_kerberos_identity_get_principal_name (GsdKerberosIdentity *self) +{ + krb5_principal principal; + krb5_error_code error_code; + char *unparsed_name; + char *principal_name; + int flags; + + if (self->priv->identifier == NULL) { + return NULL; + } + + error_code = krb5_parse_name (self->priv->kerberos_context, + self->priv->identifier, + &principal); + + if (error_code != 0) { + const char *error_message; + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error parsing identity %s into kerberos principal: %s", + self->priv->identifier, + error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return NULL; + } + + flags = KRB5_PRINCIPAL_UNPARSE_DISPLAY; + error_code = krb5_unparse_name_flags (self->priv->kerberos_context, + principal, + flags, + &unparsed_name); + + if (error_code != 0) { + const char *error_message; + + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error parsing principal identity name: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return NULL; + } + + principal_name = g_strdup (unparsed_name); + krb5_free_unparsed_name (self->priv->kerberos_context, unparsed_name); + + return principal_name; +} + +char * +gsd_kerberos_identity_get_realm_name (GsdKerberosIdentity *self) +{ + krb5_principal principal; + krb5_error_code error_code; + krb5_data *realm; + char *realm_name; + + if (self->priv->identifier == NULL) { + return NULL; + } + + error_code = krb5_parse_name (self->priv->kerberos_context, + self->priv->identifier, + &principal); + + if (error_code != 0) { + const char *error_message; + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error parsing identity %s into kerberos principal: %s", + self->priv->identifier, + error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return NULL; + } + + realm = krb5_princ_realm (self->priv->kerberos_context, + principal); + realm_name = g_strndup (realm->data, realm->length); + krb5_free_principal (self->priv->kerberos_context, principal); + + return realm_name; +} + +static const char * +gsd_kerberos_identity_get_identifier (GsdIdentity *identity) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (identity); + + return self->priv->identifier; +} + +static char ** +gsd_kerberos_identity_get_identifier_components (GsdIdentity *identity) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (identity); + + return g_strdupv (self->priv->identifier_components); +} + +static gboolean +credentials_validate_existence (GsdKerberosIdentity *self, + krb5_principal principal, + krb5_creds *credentials) +{ + /* Checks if default principal associated with the cache has a valid + * ticket granting ticket in the passed in credentials + */ + + if (krb5_is_config_principal (self->priv->kerberos_context, + credentials->server)) { + return FALSE; + } + + /* looking for the krbtgt / REALM pair, so it should be exactly 2 items */ + if (krb5_princ_size (self->priv->kerberos_context, + credentials->server) != 2) { + return FALSE; + } + + if (!krb5_realm_compare (self->priv->kerberos_context, + credentials->server, + principal)) { + /* credentials are from some other realm */ + return FALSE; + } + + if (strncmp (credentials->server->data[0].data, + KRB5_TGS_NAME, + credentials->server->data[0].length) != 0) { + /* credentials aren't for ticket granting */ + return FALSE; + } + + if (credentials->server->data[1].length != principal->realm.length || + memcmp (credentials->server->data[1].data, + principal->realm.data, + principal->realm.length) != 0) { + /* credentials are for some other realm */ + return FALSE; + } + + return TRUE; +} + +static krb5_timestamp +get_current_time (GsdKerberosIdentity *self) +{ + krb5_timestamp current_time; + krb5_error_code error_code; + + error_code = krb5_timeofday (self->priv->kerberos_context, + ¤t_time); + + if (error_code != 0) { + const char *error_message; + + error_message = krb5_get_error_message (self->priv->kerberos_context, error_code); + g_debug ("GsdKerberosIdentity: Error getting current time: %s", error_message); + krb5_free_error_message (self->priv->kerberos_context, error_message); + return 0; + } + + return current_time; +} + +typedef struct +{ + GsdKerberosIdentity *self; + guint *idle_id; + const char *property_name; +} NotifyRequest; + +static void +clear_idle_id (NotifyRequest *request) +{ + *request->idle_id = 0; + g_slice_free (NotifyRequest, request); +} + +static gboolean +on_notify_queued (NotifyRequest *request) +{ + g_object_notify (G_OBJECT (request->self), + request->property_name); + + return FALSE; +} + +static void +queue_notify (GsdKerberosIdentity *self, + guint *idle_id, + const char *property_name) +{ + NotifyRequest *request; + + if (*idle_id != 0) { + return; + } + + request = g_slice_new0 (NotifyRequest); + request->self = self; + request->idle_id = idle_id; + request->property_name = property_name; + + *idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) + on_notify_queued, + request, + (GDestroyNotify) + clear_idle_id); +} + +static void +set_expiration_time (GsdKerberosIdentity *self, + krb5_timestamp expiration_time) +{ + if (self->priv->expiration_time != expiration_time) { + self->priv->expiration_time = expiration_time; + queue_notify (self, + &self->priv->expiration_time_idle_id, + "expiration-timestamp"); + } +} + +static gboolean +credentials_are_expired (GsdKerberosIdentity *self, + krb5_creds *credentials) +{ + krb5_timestamp current_time; + + current_time = get_current_time (self); + + set_expiration_time (self, MAX (credentials->times.endtime, + self->priv->expiration_time)); + + if (credentials->times.endtime <= current_time) { + return TRUE; + } + + return FALSE; +} + +static VerificationLevel +verify_identity (GsdKerberosIdentity *self, + GError **error) +{ + krb5_principal principal; + krb5_cc_cursor cursor; + krb5_creds credentials; + krb5_error_code error_code; + VerificationLevel verification_level; + + set_expiration_time (self, 0); + + if (self->priv->credentials_cache == NULL) { + return VERIFICATION_LEVEL_UNVERIFIED; + } + + error_code = krb5_cc_get_principal (self->priv->kerberos_context, + self->priv->credentials_cache, + &principal); + + if (error_code != 0) { + if (error_code == KRB5_CC_END) { + return VERIFICATION_LEVEL_UNVERIFIED; + } + + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_NOT_FOUND, + error_code, + _("Could not find identity in credential cache: %k")); + return VERIFICATION_LEVEL_ERROR; + } + + error_code = krb5_cc_start_seq_get (self->priv->kerberos_context, + self->priv->credentials_cache, + &cursor); + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE, + error_code, + _("Could not find identity credentials in cache: %k")); + + verification_level = VERIFICATION_LEVEL_ERROR; + goto out; + } + + verification_level = VERIFICATION_LEVEL_UNVERIFIED; + + error_code = krb5_cc_next_cred (self->priv->kerberos_context, + self->priv->credentials_cache, + &cursor, + &credentials); + + while (error_code == 0) { + if (credentials_validate_existence (self, principal, &credentials)) { + if (!credentials_are_expired (self, &credentials)) { + verification_level = VERIFICATION_LEVEL_SIGNED_IN; + } else { + verification_level = VERIFICATION_LEVEL_EXISTS; + } + } + + error_code = krb5_cc_next_cred (self->priv->kerberos_context, + self->priv->credentials_cache, + &cursor, + &credentials); + } + + if (error_code != KRB5_CC_END) { + verification_level = VERIFICATION_LEVEL_ERROR; + + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_ENUMERATING_CREDENTIALS, + error_code, + _("Could not sift through identity credentials in cache: %k")); + goto out; + } + + error_code = krb5_cc_end_seq_get (self->priv->kerberos_context, + self->priv->credentials_cache, + &cursor); + + if (error_code != 0) { + verification_level = VERIFICATION_LEVEL_ERROR; + + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_ENUMERATING_CREDENTIALS, + error_code, + _("Could not finish up sifting through identity credentials in cache: %k")); + goto out; + } +out: + krb5_free_principal (self->priv->kerberos_context, principal); + return verification_level; +} + +static gboolean +gsd_kerberos_identity_is_signed_in (GsdIdentity *identity) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (identity); + gboolean is_signed_in = FALSE; + + G_LOCK (identity_lock); + if (self->priv->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN) { + is_signed_in = TRUE; + } + G_UNLOCK (identity_lock); + + return is_signed_in; +} + +static void +identity_interface_init (GsdIdentityInterface *interface) +{ + interface->get_identifier = gsd_kerberos_identity_get_identifier; + interface->get_identifier_components = gsd_kerberos_identity_get_identifier_components; + interface->is_signed_in = gsd_kerberos_identity_is_signed_in; +} + +static void +on_expiration_alarm_fired (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_debug ("GsdKerberosIdentity: expiration alarm fired for identity %s", + gsd_identity_get_identifier (GSD_IDENTITY (self))); + g_signal_emit (G_OBJECT (self), signals[NEEDS_REFRESH], 0); +} + +static void +on_expiration_alarm_rearmed (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_debug ("GsdKerberosIdentity: expiration alarm rearmed"); + g_signal_emit (G_OBJECT (self), signals[NEEDS_REFRESH], 0); +} + +static void +on_renewal_alarm_rearmed (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_debug ("GsdKerberosIdentity: renewal alarm rearmed"); +} + +static void +on_renewal_alarm_fired (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_clear_object (&self->priv->renewal_alarm_cancellable); + + if (self->priv->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN) { + g_debug ("GsdKerberosIdentity: renewal alarm fired for signed-in identity"); + g_signal_emit (G_OBJECT (self), signals[NEEDS_RENEWAL], 0); + } +} + +static void +on_expiring_alarm_rearmed (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_debug ("GsdKerberosIdentity: expiring alarm rearmed"); +} + +static void +on_expiring_alarm_fired (GsdAlarm *alarm, + GsdKerberosIdentity *self) +{ + g_return_if_fail (GSD_IS_ALARM (alarm)); + g_return_if_fail (GSD_IS_KERBEROS_IDENTITY (self)); + + g_clear_object (&self->priv->expiring_alarm_cancellable); + + if (self->priv->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN) { + g_debug ("GsdKerberosIdentity: expiring alarm fired for signed-in identity"); + g_signal_emit (G_OBJECT (self), signals[EXPIRING], 0); + } +} + +static void +set_alarm (GsdKerberosIdentity *self, + GsdAlarm *alarm, + GDateTime *alarm_time, + GCancellable **cancellable) +{ + GDateTime *old_alarm_time; + + G_LOCK (identity_lock); + old_alarm_time = gsd_alarm_get_time (alarm); + if (old_alarm_time == NULL || + !g_date_time_equal (alarm_time, old_alarm_time)) { + GCancellable *new_cancellable; + + new_cancellable = g_cancellable_new (); + gsd_alarm_set_time (alarm, + alarm_time, + new_cancellable); + g_date_time_unref (alarm_time); + + g_clear_object (cancellable); + *cancellable = new_cancellable; + } + G_UNLOCK (identity_lock); + +} + +static void +disconnect_alarm_signals (GsdKerberosIdentity *self) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->renewal_alarm), + G_CALLBACK (on_renewal_alarm_fired), + self); + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->renewal_alarm), + G_CALLBACK (on_renewal_alarm_rearmed), + self); + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->expiring_alarm), + G_CALLBACK (on_expiring_alarm_fired), + self); + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->expiration_alarm), + G_CALLBACK (on_expiration_alarm_rearmed), + self); + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->expiration_alarm), + G_CALLBACK (on_expiration_alarm_fired), + self); + g_signal_handlers_disconnect_by_func (G_OBJECT (self->priv->expiring_alarm), + G_CALLBACK (on_expiring_alarm_rearmed), + self); +} + +static void +connect_alarm_signals (GsdKerberosIdentity *self) +{ + g_signal_connect (G_OBJECT (self->priv->renewal_alarm), + "fired", + G_CALLBACK (on_renewal_alarm_fired), + self); + g_signal_connect (G_OBJECT (self->priv->renewal_alarm), + "rearmed", + G_CALLBACK (on_renewal_alarm_rearmed), + self); + g_signal_connect (G_OBJECT (self->priv->expiring_alarm), + "fired", + G_CALLBACK (on_expiring_alarm_fired), + self); + g_signal_connect (G_OBJECT (self->priv->expiring_alarm), + "rearmed", + G_CALLBACK (on_expiring_alarm_rearmed), + self); + g_signal_connect (G_OBJECT (self->priv->expiration_alarm), + "fired", + G_CALLBACK (on_expiration_alarm_fired), + self); + g_signal_connect (G_OBJECT (self->priv->expiration_alarm), + "rearmed", + G_CALLBACK (on_expiration_alarm_rearmed), + self); +} + +static void +reset_alarms (GsdKerberosIdentity *self) +{ + GDateTime *now; + GDateTime *expiration_time; + GDateTime *expiring_time; + GDateTime *renewal_time; + GTimeSpan time_span_until_expiration; + + now = g_date_time_new_now_local (); + expiration_time = g_date_time_new_from_unix_local (self->priv->expiration_time); + time_span_until_expiration = g_date_time_difference (expiration_time, now); + + /* Let the user reauthenticate 10 min before expiration */ + expiring_time = g_date_time_add_minutes (expiration_time, -10); + + /* Try to quietly auto-renew halfway through so in ideal configurations + * the ticket is never more than halfway to expired + */ + renewal_time = g_date_time_add (expiration_time, + - (time_span_until_expiration / 2)); + + disconnect_alarm_signals (self); + + set_alarm (self, + self->priv->renewal_alarm, + renewal_time, + &self->priv->renewal_alarm_cancellable); + set_alarm (self, + self->priv->expiring_alarm, + expiring_time, + &self->priv->expiring_alarm_cancellable); + set_alarm (self, + self->priv->expiration_alarm, + expiration_time, + &self->priv->expiration_alarm_cancellable); + + connect_alarm_signals (self); +} + +static void +cancel_and_clear_cancellable (GCancellable **cancellable) +{ + if (cancellable == NULL) { + return; + } + + if (!g_cancellable_is_cancelled (*cancellable)) { + g_cancellable_cancel (*cancellable); + } + + g_clear_object (cancellable); +} + +static void +clear_alarms (GsdKerberosIdentity *self) +{ + cancel_and_clear_cancellable (&self->priv->renewal_alarm_cancellable); + cancel_and_clear_cancellable (&self->priv->expiring_alarm_cancellable); + cancel_and_clear_cancellable (&self->priv->expiration_alarm_cancellable); +} + +static gboolean +gsd_kerberos_identity_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsdKerberosIdentity *self = GSD_KERBEROS_IDENTITY (initable); + GError *verification_error; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + if (self->priv->identifier == NULL) { + self->priv->identifier = get_identifier (self, error); + + if (self->priv->identifier == NULL) { + return FALSE; + } + + queue_notify (self, + &self->priv->identifier_idle_id, + "identifier"); + } + + if (self->priv->identifier_components == NULL) { + self->priv->identifier_components = get_identifier_components (self); + } + + verification_error = NULL; + self->priv->cached_verification_level = verify_identity (self, &verification_error); + + switch (self->priv->cached_verification_level) { + case VERIFICATION_LEVEL_EXISTS: + case VERIFICATION_LEVEL_SIGNED_IN: + reset_alarms (self); + + queue_notify (self, + &self->priv->is_signed_in_idle_id, + "is-signed-in"); + return TRUE; + + case VERIFICATION_LEVEL_UNVERIFIED: + return TRUE; + + case VERIFICATION_LEVEL_ERROR: + if (verification_error != NULL) { + g_propagate_error (error, verification_error); + return FALSE; + } + default: + g_set_error (error, + GSD_IDENTITY_ERROR, + GSD_IDENTITY_ERROR_VERIFYING, + _("No associated identification found")); + return FALSE; + + } +} + +static void +initable_interface_init (GInitableIface *interface) +{ + interface->init = gsd_kerberos_identity_initable_init; +} + +typedef struct +{ + GsdKerberosIdentity *identity; + GsdIdentityInquiryFunc inquiry_func; + gpointer inquiry_data; + GDestroyNotify destroy_notify; + GCancellable *cancellable; +} SignInOperation; + +static krb5_error_code +on_kerberos_inquiry (krb5_context kerberos_context, + SignInOperation *operation, + const char *name, + const char *banner, + int number_of_prompts, + krb5_prompt prompts[]) +{ + GsdIdentityInquiry *inquiry; + krb5_error_code error_code; + + inquiry = gsd_kerberos_identity_inquiry_new (operation->identity, + name, + banner, + prompts, + number_of_prompts); + + operation->inquiry_func (inquiry, + operation->cancellable, + operation->inquiry_data); + + if (g_cancellable_is_cancelled (operation->cancellable)) { + error_code = KRB5_LIBOS_PWDINTR; + } else if (!gsd_identity_inquiry_is_complete (inquiry)) { + error_code = KRB5_LIBOS_PWDINTR; + } else { + error_code = 0; + } + + g_object_unref (inquiry); + + return error_code; +} + +static gboolean +gsd_kerberos_identity_update_credentials (GsdKerberosIdentity *self, + krb5_principal principal, + krb5_creds *new_credentials, + GError **error) +{ + krb5_error_code error_code; + + error_code = krb5_cc_initialize (self->priv->kerberos_context, + self->priv->credentials_cache, + principal); + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_ALLOCATING_CREDENTIALS, + error_code, + _("Could not initialize credentials cache: %k")); + + krb5_free_cred_contents (self->priv->kerberos_context, new_credentials); + goto out; + } + + error_code = krb5_cc_store_cred (self->priv->kerberos_context, + self->priv->credentials_cache, + new_credentials); + + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_SAVING_CREDENTIALS, + error_code, + _("Could not store new credentials in credentials cache: %k")); + + krb5_free_cred_contents (self->priv->kerberos_context, new_credentials); + goto out; + } + krb5_free_cred_contents (self->priv->kerberos_context, new_credentials); + + return TRUE; +out: + return FALSE; +} + +static SignInOperation * +sign_in_operation_new (GsdKerberosIdentity *identity, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GDestroyNotify destroy_notify, + GCancellable *cancellable) +{ + SignInOperation *operation; + + operation = g_slice_new0 (SignInOperation); + operation->identity = g_object_ref (identity); + operation->inquiry_func = inquiry_func; + operation->inquiry_data = inquiry_data; + operation->destroy_notify = destroy_notify; + + if (cancellable == NULL) { + operation->cancellable = g_cancellable_new (); + } else { + operation->cancellable = g_object_ref (cancellable); + } + + return operation; +} + +static void +sign_in_operation_free (SignInOperation *operation) +{ + g_object_unref (operation->identity); + g_object_unref (operation->cancellable); + + g_slice_free (SignInOperation, operation); +} + +gboolean +gsd_kerberos_identity_sign_in (GsdKerberosIdentity *self, + const char *principal_name, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GDestroyNotify destroy_notify, + GCancellable *cancellable, + GError **error) +{ + SignInOperation *operation; + krb5_principal principal; + krb5_error_code error_code; + krb5_creds new_credentials; + krb5_get_init_creds_opt *options; + krb5_deltat start_time; + char *service_name; + char *password; + gboolean signed_in; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + error_code = krb5_get_init_creds_opt_alloc (self->priv->kerberos_context, + &options); + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_ALLOCATING_CREDENTIALS, + error_code, + "%k"); + if (destroy_notify) { + destroy_notify (inquiry_data); + } + return FALSE; + } + + signed_in = FALSE; + + operation = sign_in_operation_new (self, + inquiry_func, + inquiry_data, + destroy_notify, + cancellable); + + if (g_strcmp0 (self->priv->identifier, principal_name) != 0) { + g_free (self->priv->identifier); + self->priv->identifier = g_strdup (principal_name); + } + + error_code = krb5_parse_name (self->priv->kerberos_context, + principal_name, + &principal); + + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_PARSING_IDENTIFIER, + error_code, + "%k"); + if (destroy_notify) { + destroy_notify (inquiry_data); + } + return FALSE; + } + + /* FIXME: get from keyring if so configured */ + password = NULL; + + krb5_get_init_creds_opt_set_forwardable (options, TRUE); + krb5_get_init_creds_opt_set_proxiable (options, TRUE); + krb5_get_init_creds_opt_set_renew_life (options, G_MAXINT); + + start_time = 0; + service_name = NULL; + error_code = krb5_get_init_creds_password (self->priv->kerberos_context, + &new_credentials, + principal, + password, + (krb5_prompter_fct) + on_kerberos_inquiry, + operation, + start_time, + service_name, + options); + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_AUTHENTICATION_FAILED, + error_code, + "%k"); + if (destroy_notify) { + destroy_notify (inquiry_data); + } + sign_in_operation_free (operation); + + krb5_free_principal (self->priv->kerberos_context, principal); + goto done; + } + + if (destroy_notify) { + destroy_notify (inquiry_data); + } + sign_in_operation_free (operation); + + if (!gsd_kerberos_identity_update_credentials (self, + principal, + &new_credentials, + error)) { + krb5_free_principal (self->priv->kerberos_context, principal); + goto done; + } + krb5_free_principal (self->priv->kerberos_context, principal); + + g_debug ("GsdKerberosIdentity: identity signed in"); + signed_in = TRUE; +done: + + return signed_in; +} + +static void +update_identifier (GsdKerberosIdentity *self, + GsdKerberosIdentity *new_identity) +{ + char *new_identifier; + + new_identifier = get_identifier (self, NULL); + if (g_strcmp0 (self->priv->identifier, new_identifier) != 0) { + g_free (self->priv->identifier); + self->priv->identifier = new_identifier; + queue_notify (self, + &self->priv->identifier_idle_id, + "identifier"); + } else { + g_free (new_identifier); + } +} + +static void +update_identifier_components (GsdKerberosIdentity *self, + GsdKerberosIdentity *new_identity) +{ + char **new_components; + + new_components = get_identifier_components (self); + + if (g_strv_length (self->priv->identifier_components) == + g_strv_length (new_components)) { + int i; + + for (i = 0; new_components[i] != NULL; i++) { + if (g_strcmp0 (self->priv->identifier_components[i], + new_components[i]) != 0) { + break; + } else { + g_strfreev (new_components); + return; + } + } + } + + g_strfreev (self->priv->identifier_components); + self->priv->identifier_components = new_components; +} + +void +gsd_kerberos_identity_update (GsdKerberosIdentity *self, + GsdKerberosIdentity *new_identity) +{ + VerificationLevel verification_level; + + if (self->priv->credentials_cache != NULL) { + krb5_cc_close (self->priv->kerberos_context, self->priv->credentials_cache); + } + + krb5_cc_dup (new_identity->priv->kerberos_context, + new_identity->priv->credentials_cache, + &self->priv->credentials_cache); + + G_LOCK (identity_lock); + update_identifier (self, new_identity); + update_identifier_components (self, new_identity); + G_UNLOCK (identity_lock); + + verification_level = verify_identity (self, NULL); + + if (verification_level == VERIFICATION_LEVEL_SIGNED_IN) { + reset_alarms (self); + } else { + clear_alarms (self); + } + + if (verification_level != self->priv->cached_verification_level) { + if (self->priv->cached_verification_level == VERIFICATION_LEVEL_SIGNED_IN && + verification_level == VERIFICATION_LEVEL_EXISTS) { + + G_LOCK (identity_lock); + self->priv->cached_verification_level = verification_level; + G_UNLOCK (identity_lock); + + g_signal_emit (G_OBJECT (self), signals[EXPIRED], 0); + } if (self->priv->cached_verification_level == VERIFICATION_LEVEL_EXISTS && + verification_level == VERIFICATION_LEVEL_SIGNED_IN) { + + G_LOCK (identity_lock); + self->priv->cached_verification_level = verification_level; + G_UNLOCK (identity_lock); + + g_signal_emit (G_OBJECT (self), signals[UNEXPIRED], 0); + } + queue_notify (self, + &self->priv->is_signed_in_idle_id, + "is-signed-in"); + } +} + +gboolean +gsd_kerberos_identity_renew (GsdKerberosIdentity *self, + GError **error) +{ + krb5_error_code error_code = 0; + krb5_principal principal; + krb5_creds new_credentials; + gboolean renewed = FALSE; + char *name = NULL; + + if (self->priv->credentials_cache == NULL) { + g_set_error (error, + GSD_IDENTITY_ERROR, + GSD_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE, + _("Could not renew identitys: Not signed in")); + goto out; + } + + error_code = krb5_cc_get_principal (self->priv->kerberos_context, + self->priv->credentials_cache, + &principal); + + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_CREDENTIALS_UNAVAILABLE, + error_code, + _("Could not renew identity: %k")); + goto out; + } + + name = gsd_kerberos_identity_get_principal_name (self); + + error_code = krb5_get_renewed_creds (self->priv->kerberos_context, + &new_credentials, + principal, + self->priv->credentials_cache, + NULL); + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_RENEWING, + error_code, + _("Could not get new credentials to renew identity %s: %k"), + name); + krb5_free_principal (self->priv->kerberos_context, principal); + goto out; + } + + if (!gsd_kerberos_identity_update_credentials (self, + principal, + &new_credentials, + error)) { + krb5_free_principal (self->priv->kerberos_context, principal); + goto out; + } + + g_debug ("GsdKerberosIdentity: identity %s renewed", name); + renewed = TRUE; +out: + g_free (name); + + return renewed; +} + +gboolean +gsd_kerberos_identity_erase (GsdKerberosIdentity *self, + GError **error) +{ + krb5_error_code error_code = 0; + + if (self->priv->credentials_cache != NULL) { + error_code = krb5_cc_destroy (self->priv->kerberos_context, + self->priv->credentials_cache); + self->priv->credentials_cache = NULL; + } + + if (error_code != 0) { + set_error_from_krb5_error_code (self, + error, + GSD_IDENTITY_ERROR_REMOVING_CREDENTIALS, + error_code, + _("Could not erase identity: %k")); + return FALSE; + } + + return TRUE; +} + +GsdIdentity * +gsd_kerberos_identity_new (krb5_context context, + krb5_ccache cache) +{ + GsdKerberosIdentity *self; + GError *error; + + self = GSD_KERBEROS_IDENTITY (g_object_new (GSD_TYPE_KERBEROS_IDENTITY, NULL)); + + krb5_cc_dup (context, + cache, + &self->priv->credentials_cache); + self->priv->kerberos_context = context; + + error = NULL; + if (!g_initable_init (G_INITABLE (self), NULL, &error)) { + const char *name; + + name = krb5_cc_get_name (context, + cache); + g_debug ("Could not build identity%s%s: %s", + name != NULL? " from credentials cache " : "", + name != NULL? name : "", + error->message); + g_error_free (error); + g_object_unref (self); + return NULL; + } + + return GSD_IDENTITY (self); +} diff --git a/plugins/identity/gsd-kerberos-identity.h b/plugins/identity/gsd-kerberos-identity.h new file mode 100644 index 00000000..abe6df1e --- /dev/null +++ b/plugins/identity/gsd-kerberos-identity.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Authors: Ray Strode + */ + +#ifndef __GSD_KERBEROS_IDENTITY_H__ +#define __GSD_KERBEROS_IDENTITY_H__ + +#include +#include + +#include +#include "gsd-kerberos-identity-inquiry.h" + +G_BEGIN_DECLS + +#define GSD_TYPE_KERBEROS_IDENTITY (gsd_kerberos_identity_get_type ()) +#define GSD_KERBEROS_IDENTITY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSD_TYPE_KERBEROS_IDENTITY, GsdKerberosIdentity)) +#define GSD_KERBEROS_IDENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSD_TYPE_KERBEROS_IDENTITY, GsdKerberosIdentityClass)) +#define GSD_IS_KERBEROS_IDENTITY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSD_TYPE_KERBEROS_IDENTITY)) +#define GSD_IS_KERBEROS_IDENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSD_TYPE_KERBEROS_IDENTITY)) +#define GSD_KERBEROS_IDENTITY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GSD_TYPE_KERBEROS_IDENTITY, GsdKerberosIdentityClass)) + +typedef struct _GsdKerberosIdentity GsdKerberosIdentity; +typedef struct _GsdKerberosIdentityClass GsdKerberosIdentityClass; +typedef struct _GsdKerberosIdentityPrivate GsdKerberosIdentityPrivate; +typedef enum _GsdKerberosIdentityDescriptionLevel GsdKerberosIdentityDescriptionLevel; + +enum _GsdKerberosIdentityDescriptionLevel +{ + GSD_KERBEROS_IDENTITY_DESCRIPTION_REALM, + GSD_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_AND_REALM, + GSD_KERBEROS_IDENTITY_DESCRIPTION_USERNAME_ROLE_AND_REALM +}; + +struct _GsdKerberosIdentity +{ + GObject parent; + + GsdKerberosIdentityPrivate *priv; +}; + +struct _GsdKerberosIdentityClass +{ + GObjectClass parent_class; +}; + +GType gsd_kerberos_identity_get_type (void); + +GsdIdentity *gsd_kerberos_identity_new (krb5_context kerberos_context, + krb5_ccache cache); + +gboolean gsd_kerberos_identity_sign_in (GsdKerberosIdentity *self, + const char *principal_name, + GsdIdentityInquiryFunc inquiry_func, + gpointer inquiry_data, + GDestroyNotify destroy_notify, + GCancellable *cancellable, + GError **error); +void gsd_kerberos_identity_update (GsdKerberosIdentity *identity, + GsdKerberosIdentity *new_identity); +gboolean gsd_kerberos_identity_renew (GsdKerberosIdentity *self, + GError **error); +gboolean gsd_kerberos_identity_erase (GsdKerberosIdentity *self, + GError **error); + +char *gsd_kerberos_identity_get_principal_name (GsdKerberosIdentity *self); +char *gsd_kerberos_identity_get_realm_name (GsdKerberosIdentity *self); +G_END_DECLS + +#endif /* __GSD_KERBEROS_IDENTITY_H__ */ diff --git a/plugins/identity/identity.gnome-settings-plugin.in b/plugins/identity/identity.gnome-settings-plugin.in new file mode 100644 index 00000000..1511abdd --- /dev/null +++ b/plugins/identity/identity.gnome-settings-plugin.in @@ -0,0 +1,8 @@ +[GNOME Settings Plugin] +Module=identity +IAge=0 +_Name=Identity +_Description=Identity plugin +Authors=Ray Strode +Copyright=Copyright © 2012 Red Hat, Inc. +Website= diff --git a/plugins/identity/org.gnome.Identity.xml b/plugins/identity/org.gnome.Identity.xml new file mode 100644 index 00000000..0bf63947 --- /dev/null +++ b/plugins/identity/org.gnome.Identity.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/identity/test-identity.c b/plugins/identity/test-identity.c new file mode 100644 index 00000000..43fc3098 --- /dev/null +++ b/plugins/identity/test-identity.c @@ -0,0 +1,7 @@ +#define NEW gsd_identity_test_new +#define START gsd_identity_test_start +#define STOP gsd_identity_test_stop +#define MANAGER GsdIdentityTest +#include "gsd-identity-test.h" + +#include "test-plugin.h" -- cgit v1.2.1