/*
* Copyright © 2016 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see .
*
* Author: Allison Lortie
*/
#include "permissions.h"
#include "confinement.h"
#include
#include
typedef struct {
Permissions permissions;
gint ref_count;
gchar *node;
} Application;
typedef struct {
gchar *unique_name;
Permissions permissions;
guint watch_id;
Application *application;
} ConfinedSender;
typedef struct
{
GHashTable *applications_by_id;
GHashTable *applications_by_node;
GHashTable *confined_senders_by_name;
} DConfProxy;
static DConfProxy *
dconf_proxy_get (void)
{
static DConfProxy *the_proxy;
if (the_proxy == NULL)
{
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);
}
return the_proxy;
}
static void
confined_sender_vanished (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
ConfinedSender *self = user_data;
g_assert_cmpstr (name, ==, self->unique_name);
/* TODO: lookup the application and unmerge/unref */
permissions_clear (&self->permissions);
g_bus_unwatch_name (self->watch_id);
g_free (self->unique_name);
g_slice_free (ConfinedSender, self);
}
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 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)
{
Application *application = user_data;
g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Proxy");
g_assert_cmpstr (property_name, ==, "Directory");
return g_variant_new_string (application->permissions.ipc_dir);
}
static gchar *
dconf_proxy_create_node_name (const gchar *id)
{
static int x;
return g_strdup_printf ("%d", x++);
}
static Application *
dconf_proxy_get_application (DConfProxy *self,
const gchar *id)
{
Application *application;
application = g_hash_table_lookup (self->applications_by_id, id);
if (application == NULL)
{
application = g_slice_new (Application);
permissions_init (&application->permissions);
application->permissions.app_id = g_strdup (id);
application->node = dconf_proxy_create_node_name (id);
application->ref_count = 0;
g_hash_table_insert (self->applications_by_id, application->permissions.app_id, application);
g_hash_table_insert (self->applications_by_node, application->node, application);
}
application->ref_count++;
return application;
}
static gboolean
dconf_proxy_get_confined_sender (DConfProxy *self,
GDBusConnection *connection,
const gchar *sender,
ConfinedSender **out_confined_sender)
{
g_autoptr(GVariant) reply, credentials;
ConfinedSender *confined_sender;
Permissions permissions;
gboolean is_confined;
g_assert (g_dbus_is_unique_name (sender));
reply = g_dbus_connection_call_sync (connection,
"org.freedesktop.DBus", "/",
"org.freedesktop.DBus", "GetConnectionCredentials",
g_variant_new ("(s)", sender),
G_VARIANT_TYPE ("(a{sv})"),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
if (reply == NULL)
return FALSE;
credentials = g_variant_get_child_value (reply, 0);
if (!confinement_check (credentials, &is_confined, &permissions))
return FALSE;
if (!is_confined)
{
*out_confined_sender = NULL;
return TRUE;
}
confined_sender = g_slice_new (ConfinedSender);
confined_sender->unique_name = g_strdup (sender);
confined_sender->permissions = permissions;
confined_sender->application = dconf_proxy_get_application (self, permissions.app_id);
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);
g_hash_table_insert (self->confined_senders_by_name, confined_sender->unique_name, confined_sender);
*out_confined_sender = confined_sender;
return TRUE;
}
static gboolean
dconf_proxy_check_permissions (DConfProxy *self,
GDBusConnection *connection,
const gchar *sender,
const gchar *node,
Application **out_application)
{
ConfinedSender *confined_sender;
Application *application;
/* Find out if we have a confined sender. */
if (!dconf_proxy_get_confined_sender (self, connection, sender, &confined_sender))
return FALSE;
if (confined_sender)
{
/* The only thing we are allowed to return here is the application
* that belongs to this confined sender, but in case the node was
* specified, we need to verify that it was the correct one, too.
*
* We can skip the hash table lookup here because we already have
* the node string accessible directly.
*/
if (node && !g_str_equal (node, confined_sender->application->node))
return FALSE;
application = confined_sender->application;
}
else
{
/* Unconfined sender. Lookup the application by the node ID, if
* we have it, otherwise return NULL.
*/
if (node != NULL)
application = g_hash_table_lookup (self->applications_by_node, node);
else
application = NULL;
}
*out_application = application;
return TRUE;
}
static gchar **
dconf_proxy_subtree_enumerate (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
gpointer user_data)
{
DConfProxy *proxy = user_data;
Application *application;
gchar **result;
g_assert_cmpstr (object_path, ==, "/ca/desrt/dconf/Proxy");
/* Security check */
if (!dconf_proxy_check_permissions (proxy, connection, sender, NULL, &application))
return NULL;
if (application != NULL)
{
/* Specific confined application making the request */
result = g_new (gchar *, 1 + 1);
result[0] = g_strdup (application->node);
result[1] = NULL;
}
else
{
/* Unconfined caller: list all existing nodes (ie: debugging) */
gint i;
result = (gchar **) g_hash_table_get_keys_as_array (proxy->applications_by_node, NULL);
for (i = 0; result[i]; i++)
result[i] = g_strdup (result[i]);
}
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 *proxy_interface;
DConfProxy *proxy = user_data;
GDBusInterfaceInfo **result;
Application *application;
/* GDBus bug: g_assert (g_str_equal (object_path, "/ca/desrt/dconf/Proxy")); */
/* The root node has nothing on it */
if (node == NULL)
return NULL;
/* Do the permissions check */
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;
/* Prepare the blob */
if (proxy_interface == NULL)
{
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);
}
result = g_new (GDBusInterfaceInfo *, 1 + 1);
result[0] = g_dbus_interface_info_ref (proxy_interface);
result[1] = NULL;
return result;
}
static const GDBusInterfaceVTable *
dconf_proxy_subtree_dispatch (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *node,
gpointer *out_user_data,
gpointer user_data)
{
static const GDBusInterfaceVTable vtable = {
.method_call = dconf_proxy_method_call,
.get_property = dconf_proxy_get_property
};
DConfProxy *proxy = user_data;
Application *application;
g_assert (g_str_equal (object_path, "/ca/desrt/dconf/Proxy"));
if (!dconf_proxy_check_permissions (proxy, connection, sender, node, &application))
return NULL;
if (application == NULL)
return NULL;
*out_user_data = application;
return &vtable;
}
static void
dconf_proxy_bus_acquired_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
const GDBusSubtreeVTable subtree_vtable = {
.enumerate = dconf_proxy_subtree_enumerate,
.introspect = dconf_proxy_subtree_introspect,
.dispatch = dconf_proxy_subtree_dispatch
};
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_assert_no_error (error);
}
static void
dconf_proxy_name_lost_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_error ("Unable to acquire bus name: %s. Exiting.", name);
}
int
main (int argc,
char **argv)
{
DConfProxy *proxy;
proxy = dconf_proxy_get ();
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)
g_main_context_iteration (NULL, TRUE);
}