[dconf/wip/proxy: 12/12] more proxy changes wip



commit f1e62228aa0faeeb405901f4e6aa41dd3595ec93
Author: Allison Lortie <allison humber desrt ca>
Date:   Wed Jan 18 10:23:41 2017 -0500

    more proxy changes wip

 Makefile.am                 |    2 +-
 gvdb/gvdb-builder.c         |   68 +++++-
 gvdb/gvdb-builder.h         |    4 +
 proxy/Makefile.am           |    5 +-
 proxy/confinement-flatpak.c |    2 +-
 proxy/confinement.c         |    9 +-
 proxy/permissions.c         |  107 ++++++--
 proxy/permissions.h         |   24 +-
 proxy/proxy.c               |  596 +++++++++++++++++++++++++++++++++++++------
 9 files changed, 696 insertions(+), 121 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 1b9b004..76e694f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ include Makefile.gtester
 
 ACLOCAL_AMFLAGS = -I m4
 
-SUBDIRS = shm gvdb common engine service proxy gdbus gsettings client bin docs tests
+SUBDIRS = shm gvdb common engine service gdbus gsettings client proxy bin docs tests
 
 DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
 EXTRA_DIST = trim-lcov.py m4
diff --git a/gvdb/gvdb-builder.c b/gvdb/gvdb-builder.c
index 90ea50b..fd37cd0 100644
--- a/gvdb/gvdb-builder.c
+++ b/gvdb/gvdb-builder.c
@@ -96,21 +96,85 @@ djb_hash (const gchar *key)
   return hash_value;
 }
 
+/* /a/b/ → /a/
+ * /a/b  → /a/
+ * /     → NULL
+ */
+static gchar *
+gvdb_get_parent_name (const gchar *path,
+                      gchar        separator)
+{
+  gsize len;
+
+  len = strlen (path);
+  if (len == 1)
+    return NULL;
+
+  /* '/path/name/' → '/path/name' */
+  if (path[len - 1] == separator)
+    len--;
+
+  /* '/path/name' → '/path/' */
+  while (path[len - 1] != separator)
+    len--;
+
+  return g_strndup (path, len);
+}
+
+static void
+gvdb_hash_table_setup_item_parent (GHashTable *table,
+                                   GvdbItem   *item,
+                                   gchar       separator)
+{
+  GvdbItem *parent;
+  gchar *parent_name;
+
+  parent_name = gvdb_get_parent_name (item->key, separator);
+  if (parent_name == NULL)
+    /* root node */
+    return;
+
+  parent = g_hash_table_lookup (table, parent_name);
+
+  if (parent == NULL)
+    {
+      parent = gvdb_hash_table_insert (table, parent_name);
+      gvdb_hash_table_setup_item_parent (table, parent, separator);
+    }
+
+  gvdb_item_set_parent (item, parent);
+
+  g_free (parent_name);
+}
+
 GvdbItem *
-gvdb_hash_table_insert (GHashTable  *table,
-                        const gchar *key)
+gvdb_hash_table_insert_path (GHashTable  *table,
+                             const gchar *key,
+                             gchar        separator)
 {
   GvdbItem *item;
 
+  g_assert (!separator || key[0] == separator);
+
   item = g_slice_new0 (GvdbItem);
   item->key = g_strdup (key);
   item->hash_value = djb_hash (key);
 
   g_hash_table_insert (table, g_strdup (key), item);
 
+  if (separator)
+    gvdb_hash_table_setup_item_parent (table, item, separator);
+
   return item;
 }
 
+GvdbItem *
+gvdb_hash_table_insert (GHashTable  *table,
+                        const gchar *key)
+{
+  return gvdb_hash_table_insert_path (table, key, '\0');
+}
+
 void
 gvdb_hash_table_insert_string (GHashTable  *table,
                                const gchar *key,
diff --git a/gvdb/gvdb-builder.h b/gvdb/gvdb-builder.h
index 8ec05c8..9a1647d 100644
--- a/gvdb/gvdb-builder.h
+++ b/gvdb/gvdb-builder.h
@@ -32,6 +32,10 @@ G_GNUC_INTERNAL
 GvdbItem *              gvdb_hash_table_insert                          (GHashTable    *table,
                                                                          const gchar   *key);
 G_GNUC_INTERNAL
+GvdbItem *              gvdb_hash_table_insert_path                     (GHashTable    *table,
+                                                                         const gchar   *key,
+                                                                         gchar          separator);
+G_GNUC_INTERNAL
 void                    gvdb_hash_table_insert_string                   (GHashTable    *table,
                                                                          const gchar   *key,
                                                                          const gchar   *value);
diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index 7042bf4..491a63e 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -5,9 +5,12 @@ libexec_PROGRAMS = dconf-proxy
 dbusservice_DATA = ca.desrt.dconf.Proxy.service
 
 dconf_proxy_CFLAGS = $(gio_CFLAGS)
-dconf_proxy_LDADD = $(gio_LIBS)
+dconf_proxy_LDADD = \
+       ../client/libdconf.so           \
+       $(gio_LIBS)
 
 dconf_proxy_SOURCES = \
+       ../gvdb/gvdb-builder.c          \
        confinement.c                   \
        confinement-flatpak.c           \
        confinement.h                   \
diff --git a/proxy/confinement-flatpak.c b/proxy/confinement-flatpak.c
index 6035783..a10ad69 100644
--- a/proxy/confinement-flatpak.c
+++ b/proxy/confinement-flatpak.c
@@ -210,7 +210,7 @@ confinement_check_flatpak (GVariant    *credentials,
                         g_key_file_get_string_list (keyfile, "Policy dconf", "readable", NULL, NULL));
   permission_list_init (&out_permissions->writable,
                         g_key_file_get_string_list (keyfile, "Policy dconf", "writable", NULL, NULL));
-  out_permissions->ipc_dir = g_build_filename (g_get_user_runtime_dir (), "app", appid, NULL);
+  out_permissions->ipc_dir = g_build_filename (g_get_user_runtime_dir (), "app", appid, "dconf", NULL);
   out_permissions->app_id = appid;
 
   *out_is_confined = TRUE;
diff --git a/proxy/confinement.c b/proxy/confinement.c
index 33c3059..2f1e295 100644
--- a/proxy/confinement.c
+++ b/proxy/confinement.c
@@ -36,7 +36,14 @@ confinement_check (GVariant    *credentials,
   *out_is_confined = is_confined;
 
   if (is_confined)
-    *out_confined_permissions = permissions;
+    {
+      /* Implicitly, all writable areas are also readable, so merge them
+       * into the readable list.
+       */
+      permission_list_merge (&permissions.readable, &permissions.writable);
+
+      *out_confined_permissions = permissions;
+    }
 
   return TRUE;
 }
diff --git a/proxy/permissions.c b/proxy/permissions.c
index 3201126..9b43d08 100644
--- a/proxy/permissions.c
+++ b/proxy/permissions.c
@@ -19,53 +19,107 @@
 
 #include "permissions.h"
 
-void
+static gboolean
 permission_list_add (PermissionList *self,
                      const gchar    *string)
 {
-  gsize current;
+  gsize ref_count;
+
+  ref_count = (gsize) g_hash_table_lookup (self->hash_table, string);
+
+  ref_count++;
+
+  g_hash_table_insert (self->hash_table, g_strdup (string), (gpointer) ref_count);
 
-  current = (gsize) g_hash_table_lookup (self->hash_table, string);
-  g_hash_table_insert (self->hash_table, g_strdup (string), (gpointer) (current + 1));
+  return ref_count == 1;
 }
 
-void
+static gboolean
 permission_list_remove (PermissionList *self,
                         const gchar    *string)
 {
-  gsize current;
+  gsize ref_count;
+
+  ref_count = (gsize) g_hash_table_lookup (self->hash_table, string);
+  g_assert (ref_count != 0);
 
-  current = (gsize) g_hash_table_lookup (self->hash_table, string);
-  g_assert (current != 0);
+  ref_count--;
 
-  if (current > 1)
-    g_hash_table_insert (self->hash_table, g_strdup (string), (gpointer) (current - 1));
+  if (ref_count > 0)
+    g_hash_table_insert (self->hash_table, g_strdup (string), (gpointer) ref_count);
   else
     g_hash_table_remove (self->hash_table, string);
+
+  return ref_count == 0;
 }
 
-void
+gboolean
 permission_list_merge (PermissionList *self,
                        PermissionList *to_merge)
 {
+  gboolean any_changes = FALSE;
   GHashTableIter iter;
   gpointer key;
 
   g_hash_table_iter_init (&iter, to_merge->hash_table);
   while (g_hash_table_iter_next (&iter, &key, NULL))
-    permission_list_add (self, key);
+    any_changes |= permission_list_add (self, key);
+
+  return any_changes;
 }
 
-void
+gboolean
 permission_list_unmerge (PermissionList *self,
                          PermissionList *to_unmerge)
 {
+  gboolean any_changes = FALSE;
   GHashTableIter iter;
   gpointer key;
 
   g_hash_table_iter_init (&iter, to_unmerge->hash_table);
   while (g_hash_table_iter_next (&iter, &key, NULL))
-    permission_list_remove (self, key);
+    any_changes |= permission_list_remove (self, key);
+
+  return any_changes;
+}
+
+static gboolean
+path_contains (const gchar *a,
+               const gchar *b)
+{
+  gint i;
+
+  for (i = 0; b[i]; i++)
+    if (a[i] != b[i])
+      {
+        if (a[i] == '/')
+          return TRUE;
+
+        return FALSE;
+      }
+
+  return a[i] == '\0';
+}
+
+gboolean
+permission_list_contains (PermissionList *self,
+                          const gchar    *path)
+{
+  GHashTableIter iter;
+  gpointer key;
+
+  g_hash_table_iter_init (&iter, self->hash_table);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
+    if (path_contains (key, path))
+      return TRUE;
+
+  return FALSE;
+}
+
+const gchar **
+permission_list_get_strv (PermissionList *self)
+{
+  return (const gchar **) g_hash_table_get_keys_as_array (self->hash_table, NULL);
 }
 
 void
@@ -88,8 +142,7 @@ permission_list_init (PermissionList  *self,
 void
 permission_list_clear (PermissionList *self)
 {
-  g_hash_table_unref (self->hash_table);
-  self->hash_table = NULL;
+  g_clear_pointer (&self->hash_table, g_hash_table_unref);
 }
 
 void
@@ -100,10 +153,13 @@ permissions_init (Permissions *permissions)
 }
 
 void
-permissions_clear (Permissions *permissions)
+permissions_clear (Permissions *self)
 {
-  permission_list_clear (&permissions->readable);
-  permission_list_clear (&permissions->writable);
+  g_clear_pointer (&self->app_id, g_free);
+  g_clear_pointer (&self->ipc_dir, g_free);
+
+  permission_list_clear (&self->readable);
+  permission_list_clear (&self->writable);
 }
 
 static void
@@ -116,22 +172,21 @@ merge_string (gchar       **dest,
   g_assert_cmpstr (*dest, ==, src);
 }
 
-void
+gboolean
 permissions_merge (Permissions *permissions,
                    Permissions *to_merge)
 {
   merge_string (&permissions->app_id, to_merge->app_id);
   merge_string (&permissions->ipc_dir, to_merge->ipc_dir);
 
-  permission_list_merge (&permissions->readable, &to_merge->readable);
-  permission_list_merge (&permissions->writable, &to_merge->writable);
+  return permission_list_merge (&permissions->readable, &to_merge->readable) |
+         permission_list_merge (&permissions->writable, &to_merge->writable);
 }
 
-void
+gboolean
 permissions_unmerge (Permissions *permissions,
                      Permissions *to_unmerge)
 {
-  permission_list_unmerge (&permissions->readable, &to_unmerge->readable);
-  permission_list_unmerge (&permissions->writable, &to_unmerge->writable);
+  return permission_list_unmerge (&permissions->readable, &to_unmerge->readable) |
+         permission_list_unmerge (&permissions->writable, &to_unmerge->writable);
 }
-
diff --git a/proxy/permissions.h b/proxy/permissions.h
index aab0d3b..b02eba6 100644
--- a/proxy/permissions.h
+++ b/proxy/permissions.h
@@ -32,22 +32,21 @@ typedef struct {
   PermissionList  writable;
 } Permissions;
 
-void
-permission_list_add (PermissionList *self,
-                     const gchar    *string);
-
-void
-permission_list_remove (PermissionList *self,
-                        const gchar    *string);
-
-void
+gboolean
 permission_list_merge (PermissionList *self,
                        PermissionList *to_merge);
 
-void
+gboolean
 permission_list_unmerge (PermissionList *self,
                          PermissionList *to_unmerge);
 
+gboolean
+permission_list_contains (PermissionList *self,
+                          const gchar    *path);
+
+const gchar **
+permission_list_get_strv (PermissionList *self);
+
 void
 permission_list_init (PermissionList  *self,
                       gchar          **contents);
@@ -55,16 +54,17 @@ permission_list_init (PermissionList  *self,
 void
 permission_list_clear (PermissionList *self);
 
+
 void
 permissions_init (Permissions *permissions);
 
 void
 permissions_clear (Permissions *permissions);
 
-void
+gboolean
 permissions_merge (Permissions *permissions,
                    Permissions *to_merge);
 
-void
+gboolean
 permissions_unmerge (Permissions *permissions,
                      Permissions *to_unmerge);
diff --git a/proxy/proxy.c b/proxy/proxy.c
index 04b6ea1..96a2194 100644
--- a/proxy/proxy.c
+++ b/proxy/proxy.c
@@ -17,16 +17,30 @@
  * Author: Allison Lortie <desrt desrt ca>
  */
 
+#include "../gvdb/gvdb-builder.h"
+#include "../client/dconf.h"
 #include "permissions.h"
 #include "confinement.h"
 
+#include <sys/stat.h>
+
 #include <stdio.h>
 #include <gio/gio.h>
+#include <glib-unix.h>
+
+
+typedef struct _DConfProxy DConfProxy;
 
 typedef struct {
-  Permissions  permissions;
-  gint         ref_count;
-  gchar       *node;
+  Permissions     permissions;
+  gint            ref_count;
+  gchar          *node;
+
+  GHashTable     *locks_table;
+  DConfChangeset *db0;
+  DConfChangeset *db1;
+
+  DConfProxy     *proxy; /* backref */
 } Application;
 
 typedef struct {
@@ -36,27 +50,230 @@ typedef struct {
   Application  *application;
 } ConfinedSender;
 
-typedef struct
+struct _DConfProxy
 {
+  GDBusConnection *connection;
+  guint owner_id;
+  guint subtree_id;
+  guint object_id;
+  guint sigterm_handler;
+  guint sigint_handler;
+  gboolean exit_requested;
+
   GHashTable *applications_by_id;
   GHashTable *applications_by_node;
   GHashTable *confined_senders_by_name;
-} DConfProxy;
 
-static DConfProxy *
-dconf_proxy_get (void)
+  DConfClient *client;
+  gchar **locks;
+};
+
+static gboolean
+contains (const gchar *a, const gchar *b)
 {
-  static DConfProxy *the_proxy;
+  return g_str_equal (a, b) || (g_str_has_prefix (b, a) && g_str_has_suffix (a, "/"));
+}
 
-  if (the_proxy == NULL)
+static gboolean
+list_contains (const gchar * const *list,
+               const gchar         *item)
+{
+  gint i;
+
+  for (i = 0; list[i]; i++)
+    if (contains (list[i], item))
+      return TRUE;
+
+  return FALSE;
+}
+
+static GHashTable *
+make_locks_table (const gchar * const *writable,
+                  const gchar * const *lockdown)
+{
+  GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  gint i;
+
+  /* Mark the writable paths as unlocked, but only if they are not
+   * completely contained within an admin lockdown.
+   */
+  for (i = 0; writable[i]; i++)
+    if (!list_contains (lockdown, writable[i]))
+      g_hash_table_insert (table, g_strdup (writable[i]), GUINT_TO_POINTER (FALSE));
+
+  /* For admin lockdown on paths that are inside of the writable areas,
+   * add them in as more-specific locks.
+   */
+  for (i = 0; lockdown[i]; i++)
+    if (list_contains (writable, lockdown[i]))
+      g_hash_table_insert (table, g_strdup (lockdown[i]), GUINT_TO_POINTER (TRUE));
+
+  /* Finally, if we don't have '/' explicitly unlocked, lock it. */
+  if (!g_hash_table_contains (table, "/"))
+    g_hash_table_insert (table, g_strdup ("/"), GUINT_TO_POINTER (TRUE));
+
+  return table;
+}
+
+static void
+dump_table (GHashTable *table)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_print ("Table has %d items:\n", g_hash_table_size (table));
+
+  g_hash_table_iter_init (&iter, table);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_print ("  %s -> %s\n", (gchar *) key, value == NULL ? "false" : "true");
+  g_print("\n");
+}
+
+static void
+fill_table (DConfClient    *client,
+            const gchar    *path,
+            gboolean        is_locked,
+            DConfChangeset *db0,
+            DConfChangeset *db1,
+            GHashTable     *locks_table)
+{
+  gpointer this_lock;
+
+  if (g_hash_table_lookup_extended (locks_table, path, NULL, &this_lock))
+    is_locked = GPOINTER_TO_UINT (this_lock);
+
+  if (g_str_has_suffix (path, "/"))
     {
-      the_proxy = g_slice_new (DConfProxy);
-      the_proxy->applications_by_id = g_hash_table_new (g_str_hash, g_str_equal);
-      the_proxy->applications_by_node = g_hash_table_new (g_str_hash, g_str_equal);
-      the_proxy->confined_senders_by_name = g_hash_table_new (g_str_hash, g_str_equal);
+      g_auto(GStrv) rels;
+      gint i;
+
+      rels = dconf_client_list (client, path, NULL);
+
+      for (i = 0; rels[i]; i++)
+        {
+          g_autofree gchar *full = g_strconcat (path, rels[i], NULL);
+          fill_table (client, full, is_locked, db0, db1, locks_table);
+        }
+    }
+  else
+    {
+      if (is_locked)
+        {
+          g_autoptr(GVariant) value;
+
+          value = dconf_client_read (client, path);
+          if (value)
+            dconf_changeset_set (db1, path, value);
+        }
+      else
+        {
+          g_autoptr(GVariant) v0, v1;
+
+          v0 = dconf_client_read_full (client, path, DCONF_READ_USER_VALUE, NULL);
+          v1 = dconf_client_read_full (client, path, DCONF_READ_DEFAULT_VALUE, NULL);
+
+          if (v0)
+            dconf_changeset_set (db0, path, v0);
+
+          if (v1)
+            dconf_changeset_set (db1, path, v1);
+        }
     }
+}
+
+static gboolean
+add_key (const gchar *path,
+         GVariant    *value,
+         gpointer     user_data)
+{
+  GHashTable *gvdb = user_data;
+
+  gvdb_item_set_value (gvdb_hash_table_insert_path (gvdb, path, '/'), value);
+
+  return TRUE; /* continue */
+}
+
+void
+dconf_gvdb_utils_write_file (const gchar     *filename,
+                             DConfChangeset  *database)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GHashTable) gvdb;
+
+  gvdb = gvdb_hash_table_new (NULL, NULL);
+  dconf_changeset_all (database, add_key, gvdb);
+
+  if (!gvdb_table_write_contents (gvdb, filename, FALSE, &error))
+    g_warning ("failed to write %s: %s", filename, error->message);
+}
+
+static void
+application_update_permissions (Application *self)
+{
+  const gchar **writable;
+  const gchar **readable;
+  gchar *fn;
+
+  writable = permission_list_get_strv (&self->permissions.writable);
+  readable = permission_list_get_strv (&self->permissions.readable);
+
+  g_print ("W %s\nR %s\n", g_strjoinv(",", (gchar **)writable), g_strjoinv(",", (gchar **)readable));
+
+  g_clear_pointer (&self->locks_table, g_hash_table_unref);
+  self->locks_table = make_locks_table (writable, (const gchar **) self->proxy->locks);
+  dump_table (self->locks_table);
+
+  g_clear_pointer (&self->db0, dconf_changeset_unref);
+  g_clear_pointer (&self->db1, dconf_changeset_unref);
+
+  self->db0 = dconf_changeset_new_database (NULL);
+  self->db1 = dconf_changeset_new_database (NULL);
 
-  return the_proxy;
+  g_print ("W %s\nR %s\n", g_strjoinv(",", (gchar **)writable), g_strjoinv(",", (gchar **)readable));
+
+  if(readable[0])
+  fill_table (self->proxy->client, readable[0], TRUE, self->db0, self->db1, self->locks_table);
+
+  g_print ("db0: %s\n", g_variant_print (dconf_changeset_serialise (self->db0), FALSE));
+  g_print ("db1: %s\n", g_variant_print (dconf_changeset_serialise (self->db1), FALSE));
+
+  mkdir (self->permissions.ipc_dir, 0777);
+
+  if (!readable[0])
+    return;
+
+  fn = g_strconcat (self->permissions.ipc_dir, "/0", NULL);
+  dconf_gvdb_utils_write_file (fn, self->db0);
+  g_free (fn);
+  fn = g_strconcat (self->permissions.ipc_dir, "/1", NULL);
+  dconf_gvdb_utils_write_file (fn, self->db1);
+  g_free (fn);
+
+  g_free (writable);
+  g_free (readable);
+}
+
+static void
+application_unref (Application *self)
+{
+  self->ref_count--;
+
+  if (self->ref_count == 0)
+    {
+      g_print ("Freeing app %s\n", self->permissions.app_id);
+
+      g_hash_table_remove (self->proxy->applications_by_id, self->permissions.app_id);
+      g_hash_table_remove (self->proxy->applications_by_node, self->node);
+
+      g_clear_pointer (&self->locks_table, g_hash_table_unref);
+      g_clear_pointer (&self->db0, dconf_changeset_unref);
+      g_clear_pointer (&self->db1, dconf_changeset_unref);
+
+      permissions_clear (&self->permissions);
+      g_free (self->node);
+
+      g_slice_free (Application, self);
+    }
 }
 
 static void
@@ -68,7 +285,12 @@ confined_sender_vanished (GDBusConnection *connection,
 
   g_assert_cmpstr (name, ==, self->unique_name);
 
-  /* TODO: lookup the application and unmerge/unref */
+  g_hash_table_remove (self->application->proxy->confined_senders_by_name, self->unique_name);
+
+  if (permissions_unmerge (&self->application->permissions, &self->permissions))
+    application_update_permissions (self->application);
+
+  application_unref (self->application);
 
   permissions_clear (&self->permissions);
   g_bus_unwatch_name (self->watch_id);
@@ -77,26 +299,83 @@ confined_sender_vanished (GDBusConnection *connection,
   g_slice_free (ConfinedSender, self);
 }
 
+static gboolean
+application_can_write (const gchar *path,
+                       GVariant    *value,
+                       gpointer     user_data)
+{
+  Application *application = user_data;
+
+  /* TODO: In dconf, resets are never supposed to fail.
+   *
+   * We should respond to attempts to reset paths (for example "/") by
+   * resetting the list of all writable keys under that path.  Even an
+   * attempt to explicitly reset a non-writable key should succeed, by
+   * doing nothing.
+   *
+   * For now, reject these cases completely, to prevent applications
+   * from resetting the user's data in other applications.
+   */
+
+  return permission_list_contains (&application->permissions.writable, path);
+}
+
 static void
-dconf_proxy_method_call (GDBusConnection       *connection,
-                         const gchar           *sender,
-                         const gchar           *object_path,
-                         const gchar           *interface_name,
-                         const gchar           *method_name,
-                         GVariant              *parameters,
-                         GDBusMethodInvocation *invocation,
-                         gpointer               user_data)
+dconf_proxy_endpoint_method_call (GDBusConnection       *connection,
+                                  const gchar           *sender,
+                                  const gchar           *object_path,
+                                  const gchar           *interface_name,
+                                  const gchar           *method_name,
+                                  GVariant              *parameters,
+                                  GDBusMethodInvocation *invocation,
+                                  gpointer               user_data)
 {
+  Application *application = user_data;
+
+  g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Proxy");
+
+  if (g_str_equal (method_name, "Start"))
+    {
+    }
+
+  else if (g_str_equal (method_name, "Change"))
+    {
+      g_autoptr(GError) error = NULL;
+      g_autoptr(GVariant) serialised = g_variant_new_from_bytes (G_VARIANT_TYPE ("a{smv}"),
+                                                                 g_variant_get_data_as_bytes (parameters),
+                                                                 FALSE);
+      g_variant_ref_sink (serialised);
+
+      g_autoptr(DConfChangeset) changeset = dconf_changeset_deserialise (serialised);
+
+      /* Enforce the writability constraint */
+      if (!dconf_changeset_all (changeset, application_can_write, application))
+        {
+          g_dbus_method_invocation_return_error_literal (invocation, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
+                                                         "Attempt to write to keys blocked by confinement 
policy");
+          return;
+        }
+
+      /* The write is legitimate.  Send it to dconf. */
+      if (!dconf_client_change_sync (application->proxy->client, changeset, NULL, NULL, &error))
+        {
+          g_dbus_method_invocation_return_gerror (invocation, error);
+          return;
+        }
+
+      /* Success! */
+      g_dbus_method_invocation_return_value (invocation, NULL);
+    }
 }
 
 static GVariant *
-dconf_proxy_get_property (GDBusConnection  *connection,
-                          const gchar      *sender,
-                          const gchar      *object_path,
-                          const gchar      *interface_name,
-                          const gchar      *property_name,
-                          GError          **error,
-                          gpointer          user_data)
+dconf_proxy_endpoint_get_property (GDBusConnection  *connection,
+                                   const gchar      *sender,
+                                   const gchar      *object_path,
+                                   const gchar      *interface_name,
+                                   const gchar      *property_name,
+                                   GError          **error,
+                                   gpointer          user_data)
 {
   Application *application = user_data;
 
@@ -106,6 +385,18 @@ dconf_proxy_get_property (GDBusConnection  *connection,
   return g_variant_new_string (application->permissions.ipc_dir);
 }
 
+static void
+dconf_proxy_method_call (GDBusConnection       *connection,
+                         const gchar           *sender,
+                         const gchar           *object_path,
+                         const gchar           *interface_name,
+                         const gchar           *method_name,
+                         GVariant              *parameters,
+                         GDBusMethodInvocation *invocation,
+                         gpointer               user_data)
+{
+}
+
 static gchar *
 dconf_proxy_create_node_name (const gchar *id)
 {
@@ -124,12 +415,12 @@ dconf_proxy_get_application (DConfProxy  *self,
 
   if (application == NULL)
     {
-      application = g_slice_new (Application);
+      application = g_slice_new0 (Application);
 
       permissions_init (&application->permissions);
       application->permissions.app_id = g_strdup (id);
       application->node = dconf_proxy_create_node_name (id);
-      application->ref_count = 0;
+      application->proxy = self;
 
       g_hash_table_insert (self->applications_by_id, application->permissions.app_id, application);
       g_hash_table_insert (self->applications_by_node, application->node, application);
@@ -140,6 +431,8 @@ dconf_proxy_get_application (DConfProxy  *self,
   return application;
 }
 
+
+
 static gboolean
 dconf_proxy_get_confined_sender (DConfProxy       *self,
                                  GDBusConnection  *connection,
@@ -181,7 +474,8 @@ dconf_proxy_get_confined_sender (DConfProxy       *self,
   confined_sender->watch_id = g_bus_watch_name_on_connection (connection, sender, 
G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                               NULL, 
confined_sender_vanished,confined_sender, NULL);
 
-  permissions_merge (&confined_sender->application->permissions, &confined_sender->permissions);
+  if (permissions_merge (&confined_sender->application->permissions, &confined_sender->permissions))
+    application_update_permissions (confined_sender->application);
 
   g_hash_table_insert (self->confined_senders_by_name, confined_sender->unique_name, confined_sender);
 
@@ -244,6 +538,8 @@ dconf_proxy_subtree_enumerate (GDBusConnection *connection,
   Application *application;
   gchar **result;
 
+  g_debug ("subtree enumerate: %s %s", sender, object_path);
+
   g_assert_cmpstr (object_path, ==, "/ca/desrt/dconf/Proxy");
 
   /* Security check */
@@ -270,58 +566,86 @@ dconf_proxy_subtree_enumerate (GDBusConnection *connection,
   return result;
 }
 
-static GDBusInterfaceInfo **
-dconf_proxy_subtree_introspect (GDBusConnection *connection,
-                                const gchar     *sender,
-                                const gchar     *object_path,
-                                const gchar     *node,
-                                gpointer         user_data)
+static GDBusInterfaceInfo *
+dconf_proxy_get_proxy_interface (void)
 {
-  static GDBusInterfaceInfo *proxy_interface;
-  DConfProxy *proxy = user_data;
-  GDBusInterfaceInfo **result;
-  Application *application;
+  static GDBusInterfaceInfo *interface;
 
-  /* GDBus bug: g_assert (g_str_equal (object_path, "/ca/desrt/dconf/Proxy")); */
+  if (g_once_init_enter (&interface))
+    {
+      g_autoptr(GDBusNodeInfo) node_info;
+      GError *error = NULL;
 
-  /* The root node has nothing on it */
-  if (node == NULL)
-    return NULL;
+     g_assert_no_error (error);
 
-  /* Do the permissions check */
-  if (!dconf_proxy_check_permissions (proxy, connection, sender, node, &application))
-    return NULL;
+      g_once_init_leave (&interface, g_dbus_interface_info_ref (node_info->interfaces[0]));
+    }
 
-  /* If we didn't find an application, we act as if there is no object */
-  if (application == NULL)
-    return NULL;
+  return interface;
+}
+
+static GDBusInterfaceInfo *
+dconf_proxy_get_endpoint_interface (void)
+{
+  static GDBusInterfaceInfo *interface;
 
-  /* Prepare the blob */
-  if (proxy_interface == NULL)
+  if (g_once_init_enter (&interface))
     {
-      GDBusNodeInfo *node_info;
+      g_autoptr(GDBusNodeInfo) node_info;
       GError *error = NULL;
 
       node_info = g_dbus_node_info_new_for_xml ("<node>"
-                                                 "<interface name='ca.desrt.dconf.Proxy'>"
+                                                 "<interface name='ca.desrt.dconf.Proxy.Endpoint'>"
                                                   "<property name='Directory' type='s' access='read'/>"
-                                                  "<method name='Start'/>"
-                                                  "<method name='Write'>"
+                                                  "<method name='Change'>"
                                                    "<arg direction='in' type='ay'/>"
                                                   "</method>"
                                                  "</interface>"
                                                 "</node>", &error);
       g_assert_no_error (error);
 
-      proxy_interface = g_dbus_interface_info_ref (node_info->interfaces[0]);
-      g_dbus_node_info_unref (node_info);
+      g_once_init_leave (&interface, g_dbus_interface_info_ref (node_info->interfaces[0]));
+    }
+
+  return interface;
+}
+
+static GDBusInterfaceInfo **
+dconf_proxy_subtree_introspect (GDBusConnection *connection,
+                                const gchar     *sender,
+                                const gchar     *object_path,
+                                const gchar     *node,
+                                gpointer         user_data)
+{
+  DConfProxy *proxy = user_data;
+  GDBusInterfaceInfo *result[2];
+  Application *application;
+
+  g_debug ("subtree introspect: %s %s %s", sender, object_path, node);
+
+  /* GDBus bug: g_assert (g_str_equal (object_path, "/ca/desrt/dconf/Proxy")); */
+
+  if (node != NULL)
+    {
+      /* They are attempting to introspect a specific endpoint.  Make
+       * sure they have a right to do so.
+       */
+      if (!dconf_proxy_check_permissions (proxy, connection, sender, node, &application))
+        return NULL;
+
+      /* If we didn't find an application, we act as if there is no object */
+      if (application == NULL)
+        return NULL;
+
+      result[0] = dconf_proxy_get_endpoint_interface ();
     }
+  else
+    result[0] = dconf_proxy_get_proxy_interface ();
 
-  result = g_new (GDBusInterfaceInfo *, 1 + 1);
-  result[0] = g_dbus_interface_info_ref (proxy_interface);
+  g_dbus_interface_info_ref (result[0]);
   result[1] = NULL;
 
-  return result;
+  return g_memdup (result, sizeof result);
 }
 
 static const GDBusInterfaceVTable *
@@ -334,13 +658,14 @@ dconf_proxy_subtree_dispatch (GDBusConnection *connection,
                               gpointer         user_data)
 {
   static const GDBusInterfaceVTable vtable = {
-    .method_call = dconf_proxy_method_call,
-    .get_property = dconf_proxy_get_property
+    .method_call = dconf_proxy_endpoint_method_call,
+    .get_property = dconf_proxy_endpoint_get_property
   };
   DConfProxy *proxy = user_data;
   Application *application;
 
-  g_assert (g_str_equal (object_path, "/ca/desrt/dconf/Proxy"));
+  g_debug ("subtree dispatch: %s %s %s", sender, object_path, node);
+  g_assert_cmpstr (object_path, ==, "/ca/desrt/dconf/Proxy");
 
   if (!dconf_proxy_check_permissions (proxy, connection, sender, node, &application))
     return NULL;
@@ -363,13 +688,37 @@ dconf_proxy_bus_acquired_handler (GDBusConnection *connection,
     .introspect = dconf_proxy_subtree_introspect,
     .dispatch = dconf_proxy_subtree_dispatch
   };
+  const GDBusInterfaceVTable proxy_vtable = {
+    .method_call = dconf_proxy_method_call
+  };
+  g_autoptr(GDBusNodeInfo) node_info;
   DConfProxy *proxy = user_data;
   GError *error = NULL;
 
-  g_dbus_connection_register_subtree (connection, "/ca/desrt/dconf/Proxy", &subtree_vtable,
-                                      G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
-                                      proxy, NULL, &error);
+  g_debug ("acquired bus connection, unique %s", g_dbus_connection_get_unique_name (connection));
+
+  proxy->connection = g_object_ref (connection);
+
+  node_info = g_dbus_node_info_new_for_xml ("<node>"
+                                             "<interface name='ca.desrt.dconf.Proxy'>"
+                                              "<method name='Start'>"
+                                               "<arg name='Endpoint' direction='out' type='o'/>"
+                                               "<arg name='IpcDirectory' direction='out' type='s'/>"
+                                              "</method>"
+                                             "</interface>"
+                                            "</node>", &error);
+  g_assert_no_error (error);
+
+  proxy->subtree_id = g_dbus_connection_register_subtree (connection, "/ca/desrt/dconf/Proxy", 
&subtree_vtable,
+                                                          
G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
+                                                          proxy, NULL, &error);
   g_assert_no_error (error);
+
+  /*proxy->object_id = g_dbus_connection_register_object (connection, "/ca/desrt/dconf/Proxy", 
node_info->interfaces[0],
+                                                        &proxy_vtable, proxy, NULL, &error); */
+  g_assert_no_error (error);
+
+  g_debug ("all objects successfully registered");
 }
 
 static void
@@ -377,7 +726,100 @@ dconf_proxy_name_lost_handler (GDBusConnection *connection,
                                const gchar     *name,
                                gpointer         user_data)
 {
-  g_error ("Unable to acquire bus name: %s.  Exiting.", name);
+  DConfProxy *self = user_data;
+
+  g_warning ("Unable to acquire bus name: %s.  Exiting.", name);
+  self->exit_requested = TRUE;
+}
+
+static void
+dconf_proxy_free (DConfProxy *self)
+{
+  g_debug ("freeing proxy object");
+
+  if (g_hash_table_size (self->confined_senders_by_name) != 0)
+    {
+      GHashTableIter iter;
+      gpointer value;
+
+      g_warning ("Exiting proxy with the following applications connected.  Expect problems:");
+      g_hash_table_iter_init (&iter, self->confined_senders_by_name);
+      while (g_hash_table_iter_next (&iter, NULL, &value))
+        {
+          ConfinedSender *confined_sender = value;
+
+          g_warning ("  %s (%s)", confined_sender->unique_name, confined_sender->permissions.app_id);
+          g_hash_table_iter_remove (&iter);
+
+          confined_sender_vanished (NULL, confined_sender->unique_name, confined_sender);
+        }
+    }
+
+  if (self->object_id > 0)
+    g_dbus_connection_unregister_object (self->connection, self->object_id);
+
+  if (self->subtree_id > 0)
+    g_dbus_connection_unregister_subtree (self->connection, self->subtree_id);
+
+  g_source_remove (self->sigterm_handler);
+  g_source_remove (self->sigint_handler);
+  g_bus_unown_name (self->owner_id);
+
+  g_assert_cmpint (g_hash_table_size (self->applications_by_node), ==, 0);
+  g_assert_cmpint (g_hash_table_size (self->applications_by_id), ==, 0);
+  g_assert_cmpint (g_hash_table_size (self->confined_senders_by_name), ==, 0);
+
+  g_hash_table_unref (self->applications_by_node);
+  g_hash_table_unref (self->applications_by_id);
+  g_hash_table_unref (self->confined_senders_by_name);
+
+  if (self->connection)
+    g_object_unref (self->connection);
+
+  g_slice_free (DConfProxy, self);
+}
+
+static gboolean
+dconf_proxy_request_exit (gpointer user_data)
+{
+  DConfProxy *self = user_data;
+
+  g_debug ("requested exit on signal");
+
+  self->exit_requested = TRUE;
+
+  return TRUE;
+}
+
+static DConfProxy *
+dconf_proxy_new (void)
+{
+  DConfProxy *self;
+
+  g_debug ("creating proxy object");
+
+  self = g_slice_new0 (DConfProxy);
+  self->applications_by_id = g_hash_table_new (g_str_hash, g_str_equal);
+  self->applications_by_node = g_hash_table_new (g_str_hash, g_str_equal);
+  self->confined_senders_by_name = g_hash_table_new (g_str_hash, g_str_equal);
+
+  self->sigterm_handler = g_unix_signal_add (SIGTERM, dconf_proxy_request_exit, self);
+  self->sigint_handler = g_unix_signal_add (SIGINT, dconf_proxy_request_exit, self);
+
+  self->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, "ca.desrt.dconf.Proxy", G_BUS_NAME_OWNER_FLAGS_NONE,
+                                   dconf_proxy_bus_acquired_handler, NULL, dconf_proxy_name_lost_handler,
+                                   self, NULL);
+
+  self->client = dconf_client_new ();
+  self->locks = dconf_client_list_locks (self->client, "/", NULL);
+
+  return self;
+}
+
+static gboolean
+dconf_proxy_wants_to_run (DConfProxy *self)
+{
+  return !self->exit_requested;
 }
 
 int
@@ -386,12 +828,12 @@ main (int    argc,
 {
   DConfProxy *proxy;
 
-  proxy = dconf_proxy_get ();
+  proxy = dconf_proxy_new ();
 
-  g_bus_own_name (G_BUS_TYPE_SESSION, "ca.desrt.dconf.Proxy", G_BUS_NAME_OWNER_FLAGS_NONE,
-                  dconf_proxy_bus_acquired_handler, NULL, dconf_proxy_name_lost_handler,
-                  proxy, NULL);
-
-  while (TRUE)
+  while (dconf_proxy_wants_to_run (proxy))
     g_main_context_iteration (NULL, TRUE);
+
+  dconf_proxy_free (proxy);
+
+  return 0;
 }



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