[gnome-online-accounts/wip/rishi/identity-kernel-keyring-notification: 2/2] kerberos-identity-manager: Use Linux's notification pipe for KEYRING



commit 0e47a0014eb14881555bac79fa05b6c3f152240b
Author: Debarshi Ray <debarshir gnome org>
Date:   Thu Jan 30 18:39:06 2020 +0100

    kerberos-identity-manager: Use Linux's notification pipe for KEYRING
    
    So far, goa-identity-service didn't have a way to be notified about
    changes to Kerberos credentials caches stored in the Linux kernel's
    keyring. It resorted to polling the credentials caches to detect any
    changes made to them, leading to higher power consumption.
    
    The Linux kernel now offers a notification mechanism in the form of a
    special pipe that can be used to listen for changes to the keyring.
    This reduces the need to poll the credentials caches.
    
    https://gitlab.gnome.org/GNOME/gnome-online-accounts/merge_requests/47

 configure.ac                                 |  12 ++
 meson.build                                  |  11 +-
 src/goaidentity/Makefile.am                  |   2 +
 src/goaidentity/goakerberosidentitymanager.c | 163 +++++++++++++++++++++++++++
 src/goaidentity/meson.build                  |   3 +-
 5 files changed, 189 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a8517acc..f44f1fb9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -31,6 +31,12 @@ AX_COMPILER_FLAGS([WARN_CFLAGS],
                   [-Wno-cast-function-type -Wno-error=cast-function-type])
 
 AC_PROG_CC
+
+AC_PROG_CC_C99
+if test x$ac_cv_prog_cc_c99 = xno; then
+   AC_MSG_ERROR([C99-compatible compiler is needed])
+fi
+
 AC_PROG_LIBTOOL
 
 PKG_PROG_PKG_CONFIG(0.16)
@@ -411,6 +417,12 @@ fi
 
 AM_CONDITIONAL(BUILD_IDENTITY_SERVICE, [test x$enable_fedora != xno || test x$enable_kerberos != xno])
 
+PKG_CHECK_MODULES([LIBKEYUTILS], [libkeyutils >= 1.6.2])
+AC_SUBST(LIBKEYUTILS_CFLAGS)
+AC_SUBST(LIBKEYUTILS_LIBS)
+
+AC_CHECK_FUNCS(pipe2)
+
 # Optional timerfd support
 AC_MSG_CHECKING([for timerfd support])
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
diff --git a/meson.build b/meson.build
index eb5eaa17..0ef95cad 100644
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,10 @@ project(
   'gnome-online-accounts', 'c',
   version: '3.35.3',
   license: 'LGPL2+',
-  default_options: 'buildtype=debugoptimized',
+  default_options: [
+    'buildtype=debugoptimized',
+    'c_std=gnu99',
+  ],
   meson_version: '>= 0.50.0'
 )
 
@@ -137,6 +140,7 @@ enable_fedora = get_option('fedora')
 if enable_fedora
   gcr_dep = dependency('gcr-3')
   krb5_dep = dependency('krb5')
+  libkeyutils_dep = dependency('libkeyutils', version: '>= 1.6.2')
 
   config_h.set('GCR_API_SUBJECT_TO_CHANGE', true)
 endif
@@ -188,6 +192,7 @@ enable_kerberos = get_option('kerberos')
 if enable_kerberos
   gcr_dep = dependency('gcr-3')
   krb5_dep = dependency('krb5')
+  libkeyutils_dep = dependency('libkeyutils', version: '>= 1.6.2')
 
   config_h.set('GCR_API_SUBJECT_TO_CHANGE', true)
 endif
@@ -225,6 +230,10 @@ config_h.set_quoted('GOA_WINDOWS_LIVE_CLIENT_ID', windows_live_client_id)
 enable_windows_live = get_option('windows_live')
 config_h.set('GOA_WINDOWS_LIVE_ENABLED', enable_windows_live)
 
+if cc.has_function('pipe2')
+  config_h.set('HAVE_PIPE2', true)
+endif
+
 # Optional timerfd support
 timerfd_support_src = '''
   #include <sys/timerfd.h>
diff --git a/src/goaidentity/Makefile.am b/src/goaidentity/Makefile.am
index 9273e80e..e729220f 100644
--- a/src/goaidentity/Makefile.am
+++ b/src/goaidentity/Makefile.am
@@ -93,6 +93,7 @@ goa_identity_service_CFLAGS =                                 \
        $(GLIB_CFLAGS)                                          \
        $(GTK_CFLAGS)                                           \
        $(KRB5_CFLAGS)                                          \
+       $(LIBKEYUTILS_CFLAGS)                                   \
        $(GCR_CFLAGS)                                           \
        $(NULL)
 
@@ -101,6 +102,7 @@ goa_identity_service_LDADD =                                        \
        $(GLIB_LIBS)                                            \
        $(GTK_LIBS)                                             \
        $(KRB5_LIBS)                                            \
+       $(LIBKEYUTILS_LIBS)                                     \
        $(GCR_LIBS)                                             \
        $(NULL)
 
diff --git a/src/goaidentity/goakerberosidentitymanager.c b/src/goaidentity/goakerberosidentitymanager.c
index 3fab0f2c..afd534f1 100644
--- a/src/goaidentity/goakerberosidentitymanager.c
+++ b/src/goaidentity/goakerberosidentitymanager.c
@@ -18,19 +18,30 @@
 
 #include "config.h"
 
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
 #include "goakerberosidentitymanager.h"
 #include "goaidentitymanager.h"
 #include "goaidentitymanagererror.h"
 #include "goaidentitymanagerprivate.h"
 #include "goakerberosidentityinquiry.h"
+#include "goalinuxnotificationstream.h"
 
+#include <errno.h>
 #include <fcntl.h>
+#include <keyutils.h>
 #include <string.h>
+#include <unistd.h>
+#include <linux/watch_queue.h>
+#include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
+#include <glib-unix.h>
 #include <gio/gio.h>
 
 #include <krb5.h>
@@ -55,6 +66,7 @@ struct _GoaKerberosIdentityManager
 
   volatile int pending_refresh_count;
 
+  guint credentials_cache_keyring_notification_id;
   guint credentials_cache_polling_timeout_id;
 };
 
@@ -120,6 +132,17 @@ G_DEFINE_TYPE_WITH_CODE (GoaKerberosIdentityManager,
                                                 initable_interface_init));
 #define FALLBACK_POLLING_INTERVAL 5
 
+enum
+{
+  WATCH_QUEUE_BUFFER_SIZE = 256
+};
+
+static const struct watch_notification_filter watch_queue_notification_filter =
+{
+  .nr_filters = 1,
+  .filters = { [0] = { .type = WATCH_TYPE_KEY_NOTIFY, .info_filter = 0, .info_mask = 0, .subtype_filter[0] = 
G_MAXUINT } }
+};
+
 static Operation *
 operation_new (GoaKerberosIdentityManager *self,
                GCancellable               *cancellable,
@@ -174,6 +197,83 @@ operation_free (Operation *operation)
   g_slice_free (Operation, operation);
 }
 
+static GSource *
+goa_kerberos_identity_manager_keyring_source_new (GError **error)
+{
+  GSource *ret = NULL;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+#ifdef HAVE_PIPE2
+  {
+    g_autoptr (GInputStream) stream = NULL;
+    gint error_code;
+    gint fds[2] = { -1, -1 };
+
+    error_code = pipe2 (fds, O_NOTIFICATION_PIPE);
+    if (error_code == -1)
+      {
+        const gchar *error_str;
+
+        error_str = g_strerror (errno);
+        g_set_error_literal (error, G_UNIX_ERROR, 0, error_str);
+        goto out;
+      }
+
+    error_code = ioctl (fds[0], IOC_WATCH_QUEUE_SET_SIZE, WATCH_QUEUE_BUFFER_SIZE);
+    if (error_code == -1)
+      {
+        const gchar *error_str;
+
+        error_str = g_strerror (errno);
+        g_set_error_literal (error, G_UNIX_ERROR, 0, error_str);
+        goto out;
+      }
+
+    error_code = ioctl(fds[0], IOC_WATCH_QUEUE_SET_FILTER, &watch_queue_notification_filter);
+    if (error_code == -1)
+      {
+        const gchar *error_str;
+
+        error_str = g_strerror (errno);
+        g_set_error_literal (error, G_UNIX_ERROR, 0, error_str);
+        goto out;
+      }
+
+    error_code = keyctl_watch_key (KEY_SPEC_SESSION_KEYRING, fds[0], 0x01);
+    if (error_code == -1)
+      {
+        const gchar *error_str;
+
+        error_str = g_strerror (errno);
+        g_set_error_literal (error, G_UNIX_ERROR, 0, error_str);
+        goto out;
+      }
+
+    error_code = keyctl_watch_key (KEY_SPEC_USER_KEYRING, fds[0], 0x02);
+    if (error_code == -1)
+      {
+        const gchar *error_str;
+
+        error_str = g_strerror (errno);
+        g_set_error_literal (error, G_UNIX_ERROR, 0, error_str);
+        goto out;
+      }
+
+    stream = goa_linux_notification_stream_new (fds[0], fds[1]);
+    ret = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (stream), NULL);
+  }
+#else
+  {
+    g_set_error_literal (error, G_UNIX_ERROR, 0, "pipe2(2) not supported");
+    goto out;
+  }
+#endif
+
+ out:
+  return ret;
+}
+
 typedef struct {
   GSourceFunc func;
   gboolean ret_val;
@@ -1320,6 +1420,41 @@ credentials_cache_file_monitor_changed (GFileMonitor               *monitor,
   schedule_refresh (self);
 }
 
+static gboolean
+credentials_cache_keyring_notification (GPollableInputStream *stream, GoaKerberosIdentityManager *self)
+{
+  gssize bytes_read;
+  guchar buffer[433];
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    bytes_read = g_pollable_input_stream_read_nonblocking (stream, buffer, sizeof (buffer), NULL, &error);
+    if (error != NULL)
+      {
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+          g_debug ("GoaKerberosIdentityManager: Keyring notification source not yet ready");
+        else
+          g_warning ("GoaKerberosIdentityManager: Could not read keyring notification source: %s", 
error->message);
+
+        goto out;
+      }
+  }
+
+  if (bytes_read != (gssize) sizeof (buffer))
+    {
+      g_warning ("GoaKerberosIdentityManager: Expected to read %" G_GSIZE_FORMAT " bytes, "
+                 "but received only %" G_GSSIZE_FORMAT " bytes",
+                 sizeof (buffer),
+                 bytes_read);
+    }
+
+  schedule_refresh (self);
+
+ out:
+  return G_SOURCE_CONTINUE;
+}
+
 static gboolean
 credentials_cache_polling_timeout (GoaKerberosIdentityManager *self)
 {
@@ -1433,6 +1568,28 @@ monitor_credentials_cache (GoaKerberosIdentityManager  *self,
 
       g_object_unref (file);
     }
+  else if (strcmp (cache_type, "KEYRING") == 0)
+    {
+      GSource *keyring_source = NULL;
+
+      monitoring_error = NULL;
+      keyring_source = goa_kerberos_identity_manager_keyring_source_new (&monitoring_error);
+      if (monitoring_error != NULL)
+        {
+          g_warning ("GoaKerberosIdentityManager: Could not monitor credentials for %s (type %s), reverting 
to polling: %s",
+                     cache_path,
+                     cache_type,
+                     monitoring_error->message);
+
+          can_monitor = FALSE;
+          g_error_free (monitoring_error);
+        }
+
+      g_source_set_callback (keyring_source, (GSourceFunc) credentials_cache_keyring_notification, self, 
NULL);
+      self->credentials_cache_keyring_notification_id = g_source_attach (keyring_source, NULL);
+
+      g_clear_pointer (&keyring_source, g_source_unref);
+    }
   else
     {
       can_monitor = FALSE;
@@ -1464,6 +1621,12 @@ stop_watching_credentials_cache (GoaKerberosIdentityManager *self)
       g_clear_object (&self->credentials_cache_file_monitor);
     }
 
+  if (self->credentials_cache_keyring_notification_id != 0)
+    {
+      g_source_remove (self->credentials_cache_keyring_notification_id);
+      self->credentials_cache_keyring_notification_id = 0;
+    }
+
   if (self->credentials_cache_polling_timeout_id != 0)
     {
       g_source_remove (self->credentials_cache_polling_timeout_id);
diff --git a/src/goaidentity/meson.build b/src/goaidentity/meson.build
index dae94d5a..8be97713 100644
--- a/src/goaidentity/meson.build
+++ b/src/goaidentity/meson.build
@@ -55,7 +55,8 @@ sources += gnome.mkenums(
 deps = [
   gcr_dep,
   krb5_dep,
-  libgoa_dep
+  libgoa_dep,
+  libkeyutils_dep,
 ]
 
 cflags = [


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]