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



commit a2e874eee5676ce976df8cda6678a99e00e81af7
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 | 227 +++++++++++++++++++++++----
 src/goaidentity/meson.build                  |   3 +-
 5 files changed, 225 insertions(+), 30 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 678db4c6..fb3c1d12 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,85 @@ 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;
+      }
+
+    close (fds[1]);
+
+    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 +1422,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)
 {
@@ -1335,7 +1472,6 @@ monitor_credentials_cache (GoaKerberosIdentityManager  *self,
   krb5_ccache default_cache;
   const char *cache_type;
   const char *cache_path;
-  GFileMonitor *monitor = NULL;
   krb5_error_code error_code;
   GError *monitoring_error = NULL;
   gboolean can_monitor = TRUE;
@@ -1359,13 +1495,6 @@ monitor_credentials_cache (GoaKerberosIdentityManager  *self,
   cache_type = krb5_cc_get_type (self->kerberos_context, default_cache);
   g_assert (cache_type != NULL);
 
-  if (strcmp (cache_type, "FILE") != 0 && strcmp (cache_type, "DIR") != 0)
-    {
-      g_warning ("GoaKerberosIdentityManager: Using polling for change notification for credential cache 
type '%s'",
-                 cache_type);
-      can_monitor = FALSE;
-    }
-
   g_free (self->credentials_cache_type);
   self->credentials_cache_type = g_strdup (cache_type);
 
@@ -1387,56 +1516,92 @@ monitor_credentials_cache (GoaKerberosIdentityManager  *self,
   if (cache_path[0] == ':')
     cache_path++;
 
-  if (can_monitor)
+  if (strcmp (cache_type, "FILE") == 0 || strcmp (cache_type, "DIR") == 0)
     {
-      GFile *file;
+      GFile *file = NULL;
+      GFileMonitor *monitor = NULL;
 
       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);
+          monitoring_error = NULL;
+          monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &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);
+            }
         }
       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);
+
+          monitoring_error = NULL;
+          monitor = g_file_monitor_directory (directory, G_FILE_MONITOR_NONE, NULL, &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_object_unref (directory);
+        }
 
+      if (monitor != NULL)
+        {
+          g_signal_connect (G_OBJECT (monitor), "changed", G_CALLBACK 
(credentials_cache_file_monitor_changed), self);
+          self->credentials_cache_file_monitor = monitor;
         }
+
       g_object_unref (file);
     }
-
-  if (monitor == NULL)
+  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",
+          g_warning ("GoaKerberosIdentityManager: Could not monitor credentials for %s (type %s), reverting 
to polling: %s",
                      cache_path,
                      cache_type,
-                     monitoring_error != NULL? monitoring_error->message : "");
-          g_clear_error (&monitoring_error);
+                     monitoring_error->message);
+
+          can_monitor = FALSE;
+          g_error_free (monitoring_error);
         }
-      can_monitor = FALSE;
+
+      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
     {
-      g_signal_connect (G_OBJECT (monitor), "changed", G_CALLBACK (credentials_cache_file_monitor_changed), 
self);
-      self->credentials_cache_file_monitor = monitor;
+      can_monitor = FALSE;
     }
 
   if (!can_monitor)
     {
+      g_warning ("GoaKerberosIdentityManager: Using polling for change notification for credential cache 
type '%s'",
+                 cache_type);
+
       self->credentials_cache_polling_timeout_id = g_timeout_add_seconds (FALLBACK_POLLING_INTERVAL,
                                                                           (GSourceFunc) 
credentials_cache_polling_timeout,
                                                                           self);
@@ -1458,6 +1623,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]