From f1e62228aa0faeeb405901f4e6aa41dd3595ec93 Mon Sep 17 00:00:00 2001 From: Allison Lortie Date: Wed, 18 Jan 2017 10:23:41 -0500 Subject: 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 */ +#include "../gvdb/gvdb-builder.h" +#include "../client/dconf.h" #include "permissions.h" #include "confinement.h" +#include + #include #include +#include + + +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 ("" - "" + "" "" - "" - "" + "" "" "" "" "", &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 ("" + "" + "" + "" + "" + "" + "" + "", &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; } -- cgit v1.2.1