summaryrefslogtreecommitdiff
path: root/dbus-proxy
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2015-07-09 18:16:44 +0200
committerAlexander Larsson <alexl@redhat.com>2015-07-10 12:15:45 +0200
commit302f88e69da195ee44d5809a7355320f0072b47e (patch)
tree30c666f3ec5e1738e3f30fa34d75622b5a8207a8 /dbus-proxy
parentef223b6a46b2426e0cafa4f58e6346f63856e873 (diff)
downloadxdg-app-302f88e69da195ee44d5809a7355320f0072b47e.tar.gz
Restructure directories and build
This moves a all source code into separate subdirs per binary. The helper and the generic stuff goes into lib/ which is then used by all the others. For now this is a completely internal library, but at some point we will probably clean it up and expose some subset. Also, we move the dbus proxy to libexecdir.
Diffstat (limited to 'dbus-proxy')
-rw-r--r--dbus-proxy/Makefile.am.inc12
-rw-r--r--dbus-proxy/dbus-proxy.c223
-rw-r--r--dbus-proxy/xdg-app-proxy.c2377
-rw-r--r--dbus-proxy/xdg-app-proxy.h60
4 files changed, 2672 insertions, 0 deletions
diff --git a/dbus-proxy/Makefile.am.inc b/dbus-proxy/Makefile.am.inc
new file mode 100644
index 0000000..42bf93d
--- /dev/null
+++ b/dbus-proxy/Makefile.am.inc
@@ -0,0 +1,12 @@
+libexec_PROGRAMS += \
+ xdg-dbus-proxy \
+ $(NULL)
+
+xdg_dbus_proxy_SOURCES = \
+ dbus-proxy/xdg-app-proxy.c \
+ dbus-proxy/xdg-app-proxy.h \
+ dbus-proxy/dbus-proxy.c \
+ $(NULL)
+
+xdg_dbus_proxy_LDADD = $(BASE_LIBS) libglnx.la
+xdg_dbus_proxy_CFLAGS = $(BASE_CFLAGS) -I$(srcdir)/dbus-proxy
diff --git a/dbus-proxy/dbus-proxy.c b/dbus-proxy/dbus-proxy.c
new file mode 100644
index 0000000..97cf5bc
--- /dev/null
+++ b/dbus-proxy/dbus-proxy.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright © 2015 Red Hat, Inc
+ *
+ * This program 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 License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "libglnx/libglnx.h"
+
+#include "xdg-app-proxy.h"
+
+GList *proxies;
+int sync_fd = -1;
+
+int
+parse_generic_args (int n_args, const char *args[])
+{
+ if (g_str_has_prefix (args[0], "--fd="))
+ {
+ const char *fd_s = args[0] + strlen("--fd=");
+ char *endptr;
+ int fd;
+
+ fd = strtol (fd_s, &endptr, 10);
+ if (fd < 0 || endptr == fd_s || *endptr != 0)
+ {
+ g_printerr ("Invalid fd %s\n", fd_s);
+ return -1;
+ }
+ sync_fd = fd;
+
+ return 1;
+ }
+ else
+ {
+ g_printerr ("Unknown argument %s\n", args[0]);
+ return -1;
+ }
+}
+
+int
+start_proxy (int n_args, const char *args[])
+{
+ g_autoptr(XdgAppProxy) proxy = NULL;
+ g_autoptr (GError) error = NULL;
+ const char *bus_address, *socket_path;
+ int n;
+
+ n = 0;
+ if (n_args < n+1 || args[n][0] == '-')
+ {
+ g_printerr ("No bus address given\n");
+ return -1;
+ }
+ bus_address = args[n++];
+
+ if (n_args < n+1 || args[n][0] == '-')
+ {
+ g_printerr ("No socket path given\n");
+ return -1;
+ }
+ socket_path = args[n++];
+
+ proxy = xdg_app_proxy_new (bus_address, socket_path);
+
+ while (n < n_args)
+ {
+ if (args[n][0] != '-')
+ break;
+
+ if (g_str_has_prefix (args[n], "--see=") ||
+ g_str_has_prefix (args[n], "--talk=") ||
+ g_str_has_prefix (args[n], "--own="))
+ {
+ XdgAppPolicy policy = XDG_APP_POLICY_SEE;
+ g_autofree char *name = NULL;
+ gboolean wildcard = FALSE;
+
+ if (args[n][2] == 't')
+ policy = XDG_APP_POLICY_TALK;
+ else if (args[n][2] == 'o')
+ policy = XDG_APP_POLICY_OWN;
+
+ name = g_strdup (strchr (args[n], '=') + 1);
+ if (g_str_has_suffix (name, ".*"))
+ {
+ name[strlen (name) - 2] = 0;
+ wildcard = TRUE;
+ }
+
+ if (name[0] == ':' || !g_dbus_is_name (name))
+ {
+ g_printerr ("'%s' is not a valid dbus name\n", name);
+ return -1;
+ }
+
+ if (wildcard)
+ xdg_app_proxy_add_wildcarded_policy (proxy, name, policy);
+ else
+ xdg_app_proxy_add_policy (proxy, name, policy);
+ }
+ else if (g_str_equal (args[n], "--log"))
+ {
+ xdg_app_proxy_set_log_messages (proxy, TRUE);
+ }
+ else if (g_str_equal (args[n], "--filter"))
+ {
+ xdg_app_proxy_set_filter (proxy, TRUE);
+ }
+ else
+ {
+ int res = parse_generic_args (n_args - n, &args[n]);
+ if (res == -1)
+ return -1;
+
+ n += res - 1; /* res - 1, because we ++ below */
+ }
+
+ n++;
+ }
+
+ if (!xdg_app_proxy_start (proxy, &error))
+ {
+ g_printerr ("Failed to start proxy for %s: %s\n", bus_address, error->message);
+ return -1;
+ }
+
+ proxies = g_list_prepend (proxies, g_object_ref (proxy));
+
+ return n;
+}
+
+static gboolean
+sync_closed_cb (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ GList *l;
+
+ for (l = proxies; l != NULL; l = l->next)
+ xdg_app_proxy_stop (XDG_APP_PROXY (l->data));
+
+ exit (0);
+ return TRUE;
+}
+
+int
+main (int argc, const char *argv[])
+{
+ GMainLoop *service_loop;
+ int n_args, res;
+ const char **args;
+
+ n_args = argc - 1;
+ args = &argv[1];
+
+ while (n_args > 0)
+ {
+ if (args[0][0] == '-')
+ {
+ res = parse_generic_args (n_args, &args[0]);
+ if (res == -1)
+ return 1;
+ }
+ else
+ {
+ res = start_proxy (n_args, args);
+ if (res == -1)
+ return 1;
+ }
+
+ g_assert (res > 0);
+ n_args -= res;
+ args += res;
+ }
+
+ if (proxies == NULL)
+ {
+ g_printerr ("No proxies specied\n");
+ return 1;
+ }
+
+ if (sync_fd >= 0)
+ {
+ ssize_t written;
+ GIOChannel *sync_channel;
+ written = write (sync_fd, "x", 1);
+ if (written != 1)
+ g_warning ("Can't write to sync socket");
+
+ sync_channel = g_io_channel_unix_new (sync_fd);
+ g_io_add_watch (sync_channel, G_IO_ERR | G_IO_HUP,
+ sync_closed_cb, NULL);
+ }
+
+ service_loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (service_loop);
+
+ g_main_loop_unref (service_loop);
+
+ return 0;
+}
diff --git a/dbus-proxy/xdg-app-proxy.c b/dbus-proxy/xdg-app-proxy.c
new file mode 100644
index 0000000..875b3fd
--- /dev/null
+++ b/dbus-proxy/xdg-app-proxy.c
@@ -0,0 +1,2377 @@
+/*
+ * Copyright © 2015 Red Hat, Inc
+ *
+ * This program 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 License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <string.h>
+
+#include "xdg-app-proxy.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <gio/gunixconnection.h>
+#include <gio/gunixfdmessage.h>
+
+/**
+ * The proxy listens to a unix domain socket, and for each new
+ * connection it opens up a new connection to a specified dbus bus
+ * address (typically the session bus) and forwards data between the
+ * two. During the authentication phase all data is forwarded as
+ * received, and additionally for the first 1 byte zero we also send
+ * the proxy credentials to the bus.
+ *
+ * Once the connection is authenticated there are two modes, filtered
+ * and unfiltered. In the unfiltered mode we just send all messages on
+ * as we receive, but in the in the filtering mode we apply a policy,
+ * which is similar to the policy supported by kdbus.
+ *
+ * The policy for the filtering consists of a mapping from well-known
+ * names to a policy that is either SEE, TALK or OWN. The default
+ * initial policy is that the the user is only allowed to TALK to the
+ * bus itself (org.freedesktop.DBus, or no destination specified), and
+ * TALK to its own unique id. All other clients are invisible. The
+ * well-known names can be specified exactly, or as a simple one-level
+ * wildcard like "org.foo.*" which matches "org.foo.bar", but not
+ * "org.foobar" or "org.foo.bar.gazonk".
+ *
+ * Polices are specified for well-known names, but they also affect
+ * the owner of that name, so that the policy for a unique id is the
+ * superset of the polices for all the names it owns. Due to technical
+ * reasons the policy for a unique name is "sticky", in that we keep
+ * the highest policy granted by a once-owned name even when the client
+ * releases that name. This is impossible to avoid in a race-free way
+ * in a proxy. But this is rarely a problem in practice, as clients
+ * rarely release names and stay on the bus.
+ *
+ * Here is a desciption of the policy levels:
+ * (all policy levels also imply the ones before it)
+ *
+ * SEE:
+ * The name/id is visible in the ListNames reply
+ * The name/id is visible in the ListActivatableNames reply
+ * You can call GetNameOwner on the name
+ * You can call NameHasOwner on the name
+ * You see NameOwnerChanged signals on the name
+ * You see NameOwnerChanged signals on the id when the client disconnects
+ * You can call the GetXXX methods on the name/id to get e.g. the peer pid
+ * You get AccessDenied rather than NameHasNoOwner when sending messages to the name/id
+ *
+ * TALK:
+ * You can send method calls and signals to the name/id
+ * You will receive broadcast signals from the name/id (if you have a match rule for them)
+ * You can call StartServiceByName on the name
+ *
+ * OWN:
+ * You are allowed to call RequestName/ReleaseName/ListQueuedOwners on the name.
+ *
+ * The policy applies only to signals and method calls. All replies
+ * (errors or method returns) are allowed once for an outstanding
+ * method call, and never otherwise.
+ *
+ * Every peer on the bus is considered priviledged, and we thus trust
+ * it. So we rely on similar proxies to be running for all untrusted
+ * clients. Any such priviledged peer is allowed to send method call
+ * or unicast signal messages to the proxied client. Once another peer
+ * sends you a message the unique id of that peer is now made visible
+ * (policy SEE) to the proxied client, allowing the client to track
+ * caller lifetimes via NameOwnerChanged signals.
+ *
+ * Differences to kdbus custom endpoint policies:
+ *
+ * * The proxy will return the credentials (like pid) of the proxy,
+ * not the real client.
+ *
+ * * Policy is not dropped when a peer releases a name.
+ *
+ * * Peers that call you become visible (SEE) (and get signals for
+ * NameOwnerChange disconnect) In kdbus currently custom endpoints
+ * never get NameOwnerChange signals for unique ids, but this is
+ * problematic as it disallows a services to track lifetimes of its
+ * clients.
+ *
+ * Mode of operation
+ *
+ * Once authenticated we receive incoming messages one at a time,
+ * and then we demarshal the message headers to make routing decisions.
+ * This means we trust the bus to do message format validation, etc.
+ * (because we don't parse the body). Also we assume that the bus verifies
+ * reply_serials, i.e. that a reply can only be sent once and by the real
+ * recipient of an previously sent method call.
+ *
+ * We don't however trust the serials from the client. We verify that
+ * they are strictly increasing to make sure the code is not confused
+ * by serials being reused.
+ *
+ * In order to track the ownership of the allowed names we hijack the
+ * connection after the initial Hello message, sending AddMatch,
+ * ListNames and GetNameOwner messages to get a proper view of who
+ * owns the names atm. Then we listen to NameOwnerChanged events for
+ * further updates. This causes a slight offset between serials in the
+ * client and serials as seen by the bus.
+ *
+ * After that the filter is strictly passive, in that we never
+ * construct our own requests. For each message received from the
+ * client we look up the type and the destination policy and make a
+ * decision to either pass it on as is, rewrite it before passing on
+ * (for instance ListName replies), drop it completely, or return a
+ * made-up reply/error to the sender.
+ *
+ * When returning a made-up reply we replace the actual message with a
+ * Ping request to the bus with the same serial and replace the resulting
+ * reply with the made up reply (with the serial from the Ping reply).
+ * This means we keep the strict message ordering and serial numbers of
+ * the bus.
+ *
+ * Policy is applied to unique ids in the following cases:
+ * * During startup we call AddWatch for signals on all policy names
+ * and wildcards (using arg0namespace) so that we get NameOwnerChanged
+ * events which we use to update the unique id policies.
+ * * During startup we create synthetic GetNameOwner requests for all
+ * normal policy names, and if there are wildcarded names we create a
+ * synthetic ListNames request and use the results of that to do further
+ * GetNameOwner for the existing names matching the wildcards. When we get
+ * replies for the GetNameOwner requests the unique id policy is updated.
+ * * When we get a method call from a unique id, it gets SEE
+ * * When we get a reply to the initial Hello request we give
+ * our own assigned unique id policy TALK.
+ *
+ * All messages sent to the bus itself are fully demarshalled
+ * and handled on a per-method basis:
+ *
+ * Hello, AddMatch, RemoveMatch, GetId: Always allowed
+ * ListNames, ListActivatableNames: Always allowed, but response filtered
+ * UpdateActivationEnvironment, BecomeMonitor: Always denied
+ * RequestName, ReleaseName, ListQueuedOwners: Only allowed if arg0 is a name with policy OWN
+ * NameHasOwner, GetNameOwner: Only pass on if arg0 is a name with policy SEE, otherwise return synthetic reply
+ * StartServiceByName: Only allowed if policy TALK on arg0
+ * GetConnectionUnixProcessID, GetConnectionCredentials,
+ * GetAdtAuditSessionData, GetConnectionSELinuxSecurityContext,
+ * GetConnectionUnixUser: Allowed if policy SEE on arg0
+ *
+ * For unknown methods, we return a synthetic error.
+ */
+
+typedef struct XdgAppProxyClient XdgAppProxyClient;
+
+XdgAppPolicy xdg_app_proxy_get_policy (XdgAppProxy *proxy, const char *name);
+
+/* We start looking ignoring the first cr-lf
+ since there is no previous line initially */
+#define AUTH_END_INIT_OFFSET 2
+#define AUTH_END_STRING "\r\nBEGIN\r\n"
+
+typedef enum {
+ EXPECTED_REPLY_NONE,
+ EXPECTED_REPLY_NORMAL,
+ EXPECTED_REPLY_HELLO,
+ EXPECTED_REPLY_FILTER,
+ EXPECTED_REPLY_FAKE_GET_NAME_OWNER,
+ EXPECTED_REPLY_FAKE_LIST_NAMES,
+ EXPECTED_REPLY_LIST_NAMES,
+ EXPECTED_REPLY_REWRITE,
+} ExpectedReplyType;
+
+typedef struct {
+ gsize size;
+ gsize pos;
+ gboolean send_credentials;
+ GList *control_messages;
+
+ guchar data[16];
+ /* data continues here */
+} Buffer;
+
+typedef struct {
+ gboolean big_endian;
+ guchar type;
+ guchar flags;
+ guint32 length;
+ guint32 serial;
+ const char *path;
+ const char *interface;
+ const char *member;
+ const char *error_name;
+ const char *destination;
+ const char *sender;
+ const char *signature;
+ gboolean has_reply_serial;
+ guint32 reply_serial;
+ guint32 unix_fds;
+} Header;
+
+typedef struct {
+ gboolean got_first_byte; /* always true on bus side */
+ gboolean closed; /* always true on bus side */
+
+ XdgAppProxyClient *client;
+ GSocketConnection *connection;
+ GSource *in_source;
+ GSource *out_source;
+
+ GBytes *extra_input_data;
+ Buffer *current_read_buffer;
+ Buffer header_buffer;
+
+ GList *buffers; /* to be sent */
+ GList *control_messages;
+
+ GHashTable *expected_replies;
+} ProxySide;
+
+struct XdgAppProxyClient {
+ GObject parent;
+
+ XdgAppProxy *proxy;
+
+ gboolean authenticated;
+ int auth_end_offset;
+
+ ProxySide client_side;
+ ProxySide bus_side;
+
+ /* Filtering data: */
+ guint32 serial_offset;
+ guint32 hello_serial;
+ guint32 last_serial;
+ GHashTable *rewrite_reply;
+ GHashTable *get_owner_reply;
+
+ GHashTable *unique_id_policy;
+};
+
+typedef struct {
+ GObjectClass parent_class;
+} XdgAppProxyClientClass;
+
+struct XdgAppProxy {
+ GSocketService parent;
+
+ gboolean log_messages;
+
+ GList *clients;
+ char *socket_path;
+ char *dbus_address;
+
+ gboolean filter;
+
+ GHashTable *wildcard_policy;
+ GHashTable *policy;
+};
+
+typedef struct {
+ GSocketServiceClass parent_class;
+} XdgAppProxyClass;
+
+
+enum {
+ PROP_0,
+
+ PROP_DBUS_ADDRESS,
+ PROP_SOCKET_PATH
+};
+
+#define XDG_APP_TYPE_PROXY xdg_app_proxy_get_type()
+#define XDG_APP_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), XDG_APP_TYPE_PROXY, XdgAppProxy))
+#define XDG_APP_IS_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XDG_APP_TYPE_PROXY))
+
+
+#define XDG_APP_TYPE_PROXY_CLIENT xdg_app_proxy_client_get_type()
+#define XDG_APP_PROXY_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), XDG_APP_TYPE_PROXY_CLIENT, XdgAppProxyClient))
+#define XDG_APP_IS_PROXY_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XDG_APP_TYPE_PROXY_CLIENT))
+
+GType xdg_app_proxy_client_get_type (void);
+
+G_DEFINE_TYPE (XdgAppProxy, xdg_app_proxy, G_TYPE_SOCKET_SERVICE)
+G_DEFINE_TYPE (XdgAppProxyClient, xdg_app_proxy_client, G_TYPE_OBJECT)
+
+static void start_reading (ProxySide *side);
+static void stop_reading (ProxySide *side);
+
+static void
+buffer_free (Buffer *buffer)
+{
+ g_list_free_full (buffer->control_messages, g_object_unref);
+ g_free (buffer);
+}
+
+static void
+free_side (ProxySide *side)
+{
+ g_clear_object (&side->connection);
+ g_clear_pointer (&side->extra_input_data, g_bytes_unref);
+
+ g_list_free_full (side->buffers, (GDestroyNotify)buffer_free);
+ g_list_free_full (side->control_messages, (GDestroyNotify)g_object_unref);
+
+ if (side->in_source)
+ g_source_destroy (side->in_source);
+ if (side->out_source)
+ g_source_destroy (side->out_source);
+
+ g_hash_table_destroy (side->expected_replies);
+}
+
+static void
+xdg_app_proxy_client_finalize (GObject *object)
+{
+ XdgAppProxyClient *client = XDG_APP_PROXY_CLIENT (object);
+
+ client->proxy->clients = g_list_remove (client->proxy->clients, client);
+ g_clear_object (&client->proxy);
+
+ g_hash_table_destroy (client->rewrite_reply);
+ g_hash_table_destroy (client->get_owner_reply);
+ g_hash_table_destroy (client->unique_id_policy);
+
+ free_side (&client->client_side);
+ free_side (&client->bus_side);
+
+ G_OBJECT_CLASS (xdg_app_proxy_client_parent_class)->finalize (object);
+}
+
+static void
+xdg_app_proxy_client_class_init (XdgAppProxyClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = xdg_app_proxy_client_finalize;
+}
+
+static void
+init_side (XdgAppProxyClient *client, ProxySide *side)
+{
+ side->got_first_byte = (side == &client->bus_side);
+ side->client = client;
+ side->header_buffer.size = 16;
+ side->header_buffer.pos = 0;
+ side->current_read_buffer = &side->header_buffer;
+ side->expected_replies = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+xdg_app_proxy_client_init (XdgAppProxyClient *client)
+{
+ init_side (client, &client->client_side);
+ init_side (client, &client->bus_side);
+
+ client->auth_end_offset = AUTH_END_INIT_OFFSET;
+ client->rewrite_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+ client->get_owner_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+ client->unique_id_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+XdgAppProxyClient *
+xdg_app_proxy_client_new (XdgAppProxy *proxy, GSocketConnection *connection)
+{
+ XdgAppProxyClient *client;
+
+ g_socket_set_blocking (g_socket_connection_get_socket (connection), FALSE);
+
+ client = g_object_new (XDG_APP_TYPE_PROXY_CLIENT, NULL);
+ client->proxy = g_object_ref (proxy);
+ client->client_side.connection = g_object_ref (connection);
+
+ proxy->clients = g_list_prepend (proxy->clients, client);
+
+ return client;
+}
+
+static XdgAppPolicy
+xdg_app_proxy_get_wildcard_policy (XdgAppProxy *proxy,
+ const char *name)
+{
+ guint wildcard_policy = 0;
+ char *dot;
+ char buffer[256];
+
+ dot = strrchr (name, '.');
+ if (dot && (dot - name) <= 255)
+ {
+ strncpy (buffer, name, dot - name);
+ buffer[dot-name] = 0;
+ wildcard_policy = GPOINTER_TO_INT (g_hash_table_lookup (proxy->wildcard_policy, buffer));
+ }
+
+ return wildcard_policy;
+}
+
+XdgAppPolicy
+xdg_app_proxy_get_policy (XdgAppProxy *proxy,
+ const char *name)
+{
+ guint policy, wildcard_policy;
+
+ policy = GPOINTER_TO_INT (g_hash_table_lookup (proxy->policy, name));
+
+ wildcard_policy = xdg_app_proxy_get_wildcard_policy (proxy, name);
+
+ return MAX (policy, wildcard_policy);
+}
+
+void
+xdg_app_proxy_set_filter (XdgAppProxy *proxy,
+ gboolean filter)
+{
+ proxy->filter = filter;
+}
+
+void
+xdg_app_proxy_set_log_messages (XdgAppProxy *proxy,
+ gboolean log)
+{
+ proxy->log_messages = log;
+}
+
+void
+xdg_app_proxy_add_policy (XdgAppProxy *proxy,
+ const char *name,
+ XdgAppPolicy policy)
+{
+ g_hash_table_replace (proxy->policy, g_strdup (name), GINT_TO_POINTER (policy));
+}
+
+void
+xdg_app_proxy_add_wildcarded_policy (XdgAppProxy *proxy,
+ const char *name,
+ XdgAppPolicy policy)
+{
+ g_hash_table_replace (proxy->wildcard_policy, g_strdup (name), GINT_TO_POINTER (policy));
+}
+
+static void
+xdg_app_proxy_finalize (GObject *object)
+{
+ XdgAppProxy *proxy = XDG_APP_PROXY (object);
+
+ if (g_socket_service_is_active (G_SOCKET_SERVICE (proxy)))
+ unlink (proxy->socket_path);
+
+ g_clear_pointer (&proxy->dbus_address, g_free);
+ g_assert (proxy->clients == NULL);
+
+ g_hash_table_destroy (proxy->policy);
+ g_hash_table_destroy (proxy->wildcard_policy);
+
+ g_free (proxy->socket_path);
+ g_free (proxy->dbus_address);
+
+ G_OBJECT_CLASS (xdg_app_proxy_parent_class)->finalize (object);
+}
+
+static void
+xdg_app_proxy_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ XdgAppProxy *proxy = XDG_APP_PROXY (object);
+
+ switch (prop_id)
+ {
+ case PROP_DBUS_ADDRESS:
+ proxy->dbus_address = g_value_dup_string (value);
+ break;
+ case PROP_SOCKET_PATH:
+ proxy->socket_path = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+xdg_app_proxy_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ XdgAppProxy *proxy = XDG_APP_PROXY (object);
+
+ switch (prop_id)
+ {
+ case PROP_DBUS_ADDRESS:
+ g_value_set_string (value, proxy->dbus_address);
+ break;
+ case PROP_SOCKET_PATH:
+ g_value_set_string (value, proxy->socket_path);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static Buffer *
+buffer_new (gsize size, Buffer *old)
+{
+ Buffer *buffer = g_malloc0 (sizeof (Buffer) + size - 16);
+
+ buffer->control_messages = NULL;
+ buffer->size = size;
+
+ if (old)
+ {
+ buffer->pos = old->pos;
+ /* Takes ownership of any old control messages */
+ buffer->control_messages = old->control_messages;
+ old->control_messages = NULL;
+
+ g_assert (size >= old->size);
+ memcpy (buffer->data, old->data, old->size);
+ }
+
+ return buffer;
+}
+
+static ProxySide *
+get_other_side (ProxySide *side)
+{
+ XdgAppProxyClient *client = side->client;
+
+ if (side == &client->client_side)
+ return &client->bus_side;
+
+ return &client->client_side;
+}
+
+static void
+side_closed (ProxySide *side)
+{
+ GSocket *socket, *other_socket;
+ ProxySide *other_side = get_other_side (side);
+
+ if (side->closed)
+ return;
+
+ socket = g_socket_connection_get_socket (side->connection);
+ g_socket_close (socket, NULL);
+ side->closed = TRUE;
+
+ other_socket = g_socket_connection_get_socket (other_side->connection);
+ if (!other_side->closed && other_side->buffers == NULL)
+ {
+ other_socket = g_socket_connection_get_socket (other_side->connection);
+ g_socket_close (other_socket, NULL);
+ other_side->closed = TRUE;
+ }
+
+ if (other_side->closed)
+ g_object_unref (side->client);
+ else
+ {
+ GError *error = NULL;
+
+ other_socket = g_socket_connection_get_socket (other_side->connection);
+ if (!g_socket_shutdown (other_socket, TRUE, FALSE, &error))
+ {
+ g_warning ("Unable to shutdown read side: %s", error->message);
+ g_error_free (error);
+ }
+ }
+}
+
+static gboolean
+buffer_read (ProxySide *side,
+ Buffer *buffer,
+ GSocket *socket)
+{
+ gssize res;
+ GInputVector v;
+ GError *error = NULL;
+ GSocketControlMessage **messages;
+ int num_messages, i;
+
+ if (side->extra_input_data)
+ {
+ gsize extra_size;
+ const guchar *extra_bytes = g_bytes_get_data (side->extra_input_data, &extra_size);
+
+ res = MIN (extra_size, buffer->size - buffer->pos);
+ memcpy (&buffer->data[buffer->pos], extra_bytes, res);
+
+ if (res < extra_size)
+ side->extra_input_data =
+ g_bytes_new_with_free_func (extra_bytes + res,
+ extra_size - res,
+ (GDestroyNotify)g_bytes_unref,
+ side->extra_input_data);
+ else
+ g_clear_pointer (&side->extra_input_data, g_bytes_unref);
+ }
+ else
+ {
+ v.buffer = &buffer->data[buffer->pos];
+ v.size = buffer->size - buffer->pos;
+
+ res = g_socket_receive_message (socket, NULL, &v, 1,
+ &messages,
+ &num_messages,
+ G_SOCKET_MSG_NONE, NULL, &error);
+ if (res < 0 && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (res <= 0)
+ {
+ if (res != 0)
+ {
+ g_debug ("Error reading from socket: %s", error->message);
+ g_error_free (error);
+ }
+
+ side_closed (side);
+ return FALSE;
+ }
+
+ for (i = 0; i < num_messages; i++)
+ buffer->control_messages = g_list_append (buffer->control_messages, messages[i]);
+
+ g_free (messages);
+ }
+
+ buffer->pos += res;
+ return TRUE;
+}
+
+static gboolean
+buffer_write (ProxySide *side,
+ Buffer *buffer,
+ GSocket *socket)
+{
+ gssize res;
+ GOutputVector v;
+ GError *error = NULL;
+ GSocketControlMessage **messages = NULL;
+ int i, n_messages;
+ GList *l;
+
+ if (buffer->send_credentials &&
+ G_IS_UNIX_CONNECTION (side->connection))
+ {
+ g_assert (buffer->size == 1);
+
+ if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (side->connection),
+ NULL,
+ &error))
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_warning ("Error writing credentials to socket: %s", error->message);
+ g_error_free (error);
+
+ side_closed (side);
+ return FALSE;
+ }
+
+ buffer->pos = 1;
+ return TRUE;
+ }
+
+ n_messages = g_list_length (buffer->control_messages);
+ messages = g_new (GSocketControlMessage *, n_messages);
+ for (l = buffer->control_messages, i = 0; l != NULL ; l = l->next, i++)
+ messages[i] = l->data;
+
+ v.buffer = &buffer->data[buffer->pos];
+ v.size = buffer->size - buffer->pos;
+
+ res = g_socket_send_message (socket, NULL, &v, 1,
+ messages, n_messages,
+ G_SOCKET_MSG_NONE, NULL, &error);
+ g_free (messages);
+ if (res < 0 && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (res <= 0)
+ {
+ if (res < 0)
+ {
+ g_warning ("Error writing credentials to socket: %s", error->message);
+ g_error_free (error);
+ }
+
+ side_closed (side);
+ return FALSE;
+ }
+
+ g_list_free_full (buffer->control_messages, g_object_unref);
+ buffer->control_messages = NULL;
+
+ buffer->pos += res;
+ return TRUE;
+}
+
+static gboolean
+side_out_cb (GSocket *socket, GIOCondition condition, gpointer user_data)
+{
+ ProxySide *side = user_data;
+ XdgAppProxyClient *client = side->client;
+ gboolean retval = G_SOURCE_CONTINUE;
+
+ g_object_ref (client);
+
+ while (side->buffers)
+ {
+ Buffer *buffer = side->buffers->data;
+
+ if (buffer_write (side, buffer, socket))
+ {
+ if (buffer->pos == buffer->size)
+ {
+ side->buffers = g_list_delete_link (side->buffers, side->buffers);
+ buffer_free (buffer);
+ }
+ }
+ else
+ break;
+ }
+
+ if (side->buffers == NULL)
+ {
+ ProxySide *other_side = get_other_side (side);
+
+ side->out_source = NULL;
+ retval = G_SOURCE_REMOVE;
+
+ if (other_side->closed)
+ side_closed (side);
+ }
+
+ g_object_unref (client);
+
+ return retval;
+}
+
+static void
+queue_expected_reply (ProxySide *side, guint32 serial, ExpectedReplyType type)
+{
+ g_hash_table_replace (side->expected_replies,
+ GUINT_TO_POINTER (serial),
+ GUINT_TO_POINTER (type));
+}
+
+static ExpectedReplyType
+steal_expected_reply (ProxySide *side, guint32 serial)
+{
+ ExpectedReplyType type;
+
+ type = GPOINTER_TO_UINT (g_hash_table_lookup (side->expected_replies,
+ GUINT_TO_POINTER (serial)));
+ if (type)
+ g_hash_table_remove (side->expected_replies,
+ GUINT_TO_POINTER (serial));
+ return type;
+}
+
+
+static void
+queue_outgoing_buffer (ProxySide *side, Buffer *buffer)
+{
+ if (side->out_source == NULL)
+ {
+ GSocket *socket;
+
+ socket = g_socket_connection_get_socket (side->connection);
+ side->out_source = g_socket_create_source (socket, G_IO_OUT, NULL);
+ g_source_set_callback (side->out_source, (GSourceFunc)side_out_cb, side, NULL);
+ g_source_attach (side->out_source, NULL);
+ g_source_unref (side->out_source);
+ }
+
+ buffer->pos = 0;
+ side->buffers = g_list_append (side->buffers, buffer);
+}
+
+static guint32
+read_uint32 (Header *header, guint8 *ptr)
+{
+ if (header->big_endian)
+ return GUINT32_FROM_BE (*(guint32 *)ptr);
+ else
+ return GUINT32_FROM_LE (*(guint32 *)ptr);
+}
+
+static void
+write_uint32 (Header *header, guint8 *ptr, guint32 val)
+{
+ if (header->big_endian)
+ *(guint32 *)ptr = GUINT32_TO_BE (val);
+ else
+ *(guint32 *)ptr = GUINT32_TO_LE (val);
+}
+
+static guint32
+align_by_8 (guint32 offset)
+{
+ return 8 * ((offset + 7)/8);
+}
+
+static guint32
+align_by_4 (guint32 offset)
+{
+ return 4 * ((offset + 3)/4);
+}
+
+static const char *
+get_signature (Buffer *buffer, guint32 *offset, guint32 end_offset)
+{
+ guint8 len;
+ char *str;
+
+ if (*offset >= end_offset)
+ return FALSE;
+
+ len = buffer->data[*offset];
+ (*offset)++;
+
+ if ((*offset) + len + 1 > end_offset)
+ return FALSE;
+
+ if (buffer->data[(*offset) + len] != 0)
+ return FALSE;
+
+ str = (char *)&buffer->data[(*offset)];
+ *offset += len + 1;
+
+ return str;
+}
+
+static const char *
+get_string (Buffer *buffer, Header *header, guint32 *offset, guint32 end_offset)
+{
+ guint8 len;
+ char *str;
+
+ *offset = align_by_4 (*offset);
+ if (*offset + 4 >= end_offset)
+ return FALSE;
+
+ len = read_uint32 (header, &buffer->data[*offset]);
+ *offset += 4;
+
+ if ((*offset) + len + 1 > end_offset)
+ return FALSE;
+
+ if (buffer->data[(*offset) + len] != 0)
+ return FALSE;
+
+ str = (char *)&buffer->data[(*offset)];
+ *offset += len + 1;
+
+ return str;
+}
+
+static gboolean
+parse_header (Buffer *buffer, Header *header, guint32 serial_offset, guint32 reply_serial_offset, guint32 hello_serial)
+{
+ guint32 array_len, header_len;
+ guint32 offset, end_offset;
+ guint8 header_type;
+ guint32 reply_serial_pos = 0;
+ const char *signature;
+
+ memset (header, 0, sizeof (Header));
+
+ if (buffer->size < 16)
+ return FALSE;
+
+ if (buffer->data[3] != 1) /* Protocol version */
+ return FALSE;
+
+ if (buffer->data[0] == 'B')
+ header->big_endian = TRUE;
+ else if (buffer->data[0] == 'l')
+ header->big_endian = FALSE;
+ else
+ return FALSE;
+
+ header->type = buffer->data[1];
+ header->flags = buffer->data[2];
+
+ header->length = read_uint32 (header, &buffer->data[4]);
+ header->serial = read_uint32 (header, &buffer->data[8]);
+
+ if (header->serial == 0)
+ return FALSE;
+
+ array_len = read_uint32 (header, &buffer->data[12]);
+
+ header_len = align_by_8 (12 + 4 + array_len);
+ g_assert (buffer->size >= header_len); /* We should have verified this when reading in the message */
+ if (header_len > buffer->size)
+ return FALSE;
+
+ offset = 12 + 4;
+ end_offset = offset + array_len;
+
+ while (offset < end_offset)
+ {
+ offset = align_by_8 (offset); /* Structs must be 8 byte aligned */
+ if (offset >= end_offset)
+ return FALSE;
+
+ header_type = buffer->data[offset++];
+ if (offset >= end_offset)
+ return FALSE;
+
+ signature = get_signature (buffer, &offset, end_offset);
+ if (signature == NULL)
+ return FALSE;
+
+ switch (header_type)
+ {
+ case G_DBUS_MESSAGE_HEADER_FIELD_INVALID:
+ return FALSE;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_PATH:
+ if (strcmp (signature, "o") != 0)
+ return FALSE;
+ header->path = get_string (buffer, header, &offset, end_offset);
+ if (header->path == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE:
+ if (strcmp (signature, "s") != 0)
+ return FALSE;
+ header->interface = get_string (buffer, header, &offset, end_offset);
+ if (header->interface == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_MEMBER:
+ if (strcmp (signature, "s") != 0)
+ return FALSE;
+ header->member = get_string (buffer, header, &offset, end_offset);
+ if (header->member == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME:
+ if (strcmp (signature, "s") != 0)
+ return FALSE;
+ header->error_name = get_string (buffer, header, &offset, end_offset);
+ if (header->error_name == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL:
+ if (offset + 4 > end_offset)
+ return FALSE;
+
+ header->has_reply_serial = TRUE;
+ reply_serial_pos = offset;
+ header->reply_serial = read_uint32 (header, &buffer->data[offset]);
+ offset += 4;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION:
+ if (strcmp (signature, "s") != 0)
+ return FALSE;
+ header->destination = get_string (buffer, header, &offset, end_offset);
+ if (header->destination == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_SENDER:
+ if (strcmp (signature, "s") != 0)
+ return FALSE;
+ header->sender = get_string (buffer, header, &offset, end_offset);
+ if (header->sender == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE:
+ if (strcmp (signature, "g") != 0)
+ return FALSE;
+ header->signature = get_signature (buffer, &offset, end_offset);
+ if (header->signature == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS:
+ if (offset + 4 > end_offset)
+ return FALSE;
+
+ header->unix_fds = read_uint32 (header, &buffer->data[offset]);
+ offset += 4;
+ break;
+
+ default:
+ /* Unknown header field, for safety, fail parse */
+ return FALSE;
+ }
+ }
+
+ switch (header->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ if (header->path == NULL || header->member == NULL)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ if (!header->has_reply_serial)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ if (header->error_name == NULL || !header->has_reply_serial)
+ return FALSE;
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ if (header->path == NULL ||
+ header->interface == NULL ||
+ header->member == NULL)
+ return FALSE;
+ if (strcmp (header->path, "/org/freedesktop/DBus/Local") == 0 ||
+ strcmp (header->interface, "org.freedesktop.DBus.Local") == 0)
+ return FALSE;
+ break;
+ default:
+ /* Unknown message type, for safety, fail parse */
+ return FALSE;
+ }
+
+ if (serial_offset > 0)
+ {
+ header->serial += serial_offset;
+ write_uint32 (header, &buffer->data[8], header->serial);
+ }
+
+ if (reply_serial_offset > 0 &&
+ header->has_reply_serial &&
+ header->reply_serial > hello_serial + reply_serial_offset)
+ {
+ write_uint32 (header, &buffer->data[reply_serial_pos], header->reply_serial - reply_serial_offset);
+ }
+
+ return TRUE;
+}
+
+static void
+print_outgoing_header (Header *header)
+{
+ switch (header->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ g_print ("C%d: -> %s call %s.%s at %s\n",
+ header->serial,
+ header->destination ? header->destination : "(no dest)",
+ header->interface ? header->interface : "",
+ header->member ? header->member : "",
+ header->path ? header->path : "");
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ g_print ("C%d: -> %s return from B%d\n",
+ header->serial,
+ header->destination ? header->destination : "(no dest)",
+ header->reply_serial);
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ g_print ("C%d: -> %s return error %s from B%d\n",
+ header->serial,
+ header->destination ? header->destination : "(no dest)",
+ header->error_name ? header->error_name : "(no error)",
+ header->reply_serial);
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ g_print ("C%d: -> %s signal %s.%s at %s\n",
+ header->serial,
+ header->destination ? header->destination : "all",
+ header->interface ? header->interface : "",
+ header->member ? header->member : "",
+ header->path ? header->path : "");
+ break;
+ default:
+ g_print ("unknown message type\n");
+ }
+}
+
+static void
+print_incoming_header (Header *header)
+{
+ switch (header->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ g_print ("B%d: <- %s call %s.%s at %s\n",
+ header->serial,
+ header->sender ? header->sender : "(no sender)",
+ header->interface ? header->interface : "",
+ header->member ? header->member : "",
+ header->path ? header->path : "");
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ g_print ("B%d: <- %s return from C%d\n",
+ header->serial,
+ header->sender ? header->sender : "(no sender)",
+ header->reply_serial);
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ g_print ("B%d: <- %s return error %s from C%d\n",
+ header->serial,
+ header->sender ? header->sender : "(no sender)",
+ header->error_name ? header->error_name : "(no error)",
+ header->reply_serial);
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ g_print ("B%d: <- %s signal %s.%s at %s\n",
+ header->serial,
+ header->sender ? header->sender : "(no sender)",
+ header->interface ? header->interface : "",
+ header->member ? header->member : "",
+ header->path ? header->path : "");
+ break;
+ default:
+ g_print ("unknown message type\n");
+ }
+}
+
+static XdgAppPolicy
+xdg_app_proxy_client_get_policy (XdgAppProxyClient *client, const char *source)
+{
+ if (source == NULL)
+ return XDG_APP_POLICY_TALK; /* All clients can talk to the bus itself */
+
+ if (source[0] == ':')
+ return GPOINTER_TO_UINT (g_hash_table_lookup (client->unique_id_policy, source));
+
+ return xdg_app_proxy_get_policy (client->proxy, source);
+}
+
+static void
+xdg_app_proxy_client_update_unique_id_policy (XdgAppProxyClient *client,
+ const char *unique_id,
+ XdgAppPolicy policy)
+{
+ if (policy > XDG_APP_POLICY_NONE)
+ {
+ XdgAppPolicy old_policy;
+ old_policy = GPOINTER_TO_UINT (g_hash_table_lookup (client->unique_id_policy, unique_id));
+ if (policy > old_policy)
+ g_hash_table_replace (client->unique_id_policy, g_strdup (unique_id), GINT_TO_POINTER (policy));
+ }
+}
+
+static void
+xdg_app_proxy_client_update_unique_id_policy_from_name (XdgAppProxyClient *client,
+ const char *unique_id,
+ const char *as_name)
+{
+ xdg_app_proxy_client_update_unique_id_policy (client,
+ unique_id,
+ xdg_app_proxy_get_policy (client->proxy, as_name));
+}
+
+
+static gboolean
+client_message_generates_reply (Header *header)
+{
+ switch (header->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ return (header->flags & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED) == 0;
+
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ default:
+ return FALSE;
+ }
+}
+
+static Buffer *
+message_to_buffer (GDBusMessage *message)
+{
+ Buffer *buffer;
+ guchar *blob;
+ gsize blob_size;
+
+ blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, NULL);
+ buffer = buffer_new (blob_size, NULL);
+ memcpy (buffer->data, blob, blob_size);
+ g_free (blob);
+
+ return buffer;
+}
+
+static GDBusMessage *
+get_error_for_header (XdgAppProxyClient *client, Header *header, const char *error)
+{
+ GDBusMessage *reply;
+
+ reply = g_dbus_message_new ();
+ g_dbus_message_set_message_type (reply, G_DBUS_MESSAGE_TYPE_ERROR);
+ g_dbus_message_set_flags (reply, G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED);
+ g_dbus_message_set_reply_serial (reply, header->serial - client->serial_offset);
+ g_dbus_message_set_error_name (reply, error);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)", error));
+
+ return reply;
+}
+
+static GDBusMessage *
+get_bool_reply_for_header (XdgAppProxyClient *client, Header *header, gboolean val)
+{
+ GDBusMessage *reply;
+
+ reply = g_dbus_message_new ();
+ g_dbus_message_set_message_type (reply, G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
+ g_dbus_message_set_flags (reply, G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED);
+ g_dbus_message_set_reply_serial (reply, header->serial - client->serial_offset);
+ g_dbus_message_set_body (reply, g_variant_new_boolean (val));
+
+ return reply;
+}
+
+static Buffer *
+get_ping_buffer_for_header (Header *header)
+{
+ Buffer *buffer;
+ GDBusMessage *dummy;
+
+ dummy = g_dbus_message_new_method_call (NULL, "/", "org.freedesktop.DBus.Peer", "Ping");
+ g_dbus_message_set_serial (dummy, header->serial);
+ g_dbus_message_set_flags (dummy, header->flags);
+
+ buffer = message_to_buffer (dummy);
+
+ g_object_unref (dummy);
+
+ return buffer;
+}
+
+static Buffer *
+get_error_for_roundtrip (XdgAppProxyClient *client, Header *header, const char *error_name)
+{
+ Buffer *ping_buffer = get_ping_buffer_for_header (header);
+ GDBusMessage *reply;
+
+ reply = get_error_for_header (client, header, error_name);
+ g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply);
+ return ping_buffer;
+}
+
+static Buffer *
+get_bool_reply_for_roundtrip (XdgAppProxyClient *client, Header *header, gboolean val)
+{
+ Buffer *ping_buffer = get_ping_buffer_for_header (header);
+ GDBusMessage *reply;
+
+ reply = get_bool_reply_for_header (client, header, val);
+ g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply);
+
+ return ping_buffer;
+}
+
+typedef enum {
+ HANDLE_PASS,
+ HANDLE_DENY,
+ HANDLE_HIDE,
+ HANDLE_FILTER_NAME_LIST_REPLY,
+ HANDLE_FILTER_HAS_OWNER_REPLY,
+ HANDLE_FILTER_GET_OWNER_REPLY,
+ HANDLE_VALIDATE_OWN,
+ HANDLE_VALIDATE_SEE,
+ HANDLE_VALIDATE_TALK,
+} BusHandler;
+
+static gboolean
+is_dbus_method_call (Header *header)
+{
+ return
+ header->type == G_DBUS_MESSAGE_TYPE_METHOD_CALL &&
+ g_strcmp0 (header->destination, "org.freedesktop.DBus") == 0 &&
+ g_strcmp0 (header->interface, "org.freedesktop.DBus") == 0;
+}
+
+static BusHandler
+get_dbus_method_handler (XdgAppProxyClient *client, Header *header)
+{
+ XdgAppPolicy policy;
+ const char *method;
+
+ if (header->has_reply_serial)
+ {
+ ExpectedReplyType expected_reply =
+ steal_expected_reply (&client->bus_side,
+ header->reply_serial);
+ if (expected_reply == EXPECTED_REPLY_NONE)
+ return HANDLE_DENY;
+
+ return HANDLE_PASS;
+ }
+
+ policy = xdg_app_proxy_client_get_policy (client, header->destination);
+ if (policy < XDG_APP_POLICY_SEE)
+ return HANDLE_HIDE;
+ if (policy < XDG_APP_POLICY_TALK)
+ return HANDLE_DENY;
+
+ if (!is_dbus_method_call (header))
+ return HANDLE_PASS;
+
+ method = header->member;
+ if (method == NULL)
+ return HANDLE_DENY;
+
+ if (strcmp (method, "Hello") == 0 ||
+ strcmp (method, "AddMatch") == 0 ||
+ strcmp (method, "RemoveMatch") == 0 ||
+ strcmp (method, "GetId") == 0)
+ return HANDLE_PASS;
+
+ if (strcmp (method, "UpdateActivationEnvironment") == 0 ||
+ strcmp (method, "BecomeMonitor") == 0)
+ return HANDLE_DENY;
+
+ if (strcmp (method, "RequestName") == 0 ||
+ strcmp (method, "ReleaseName") == 0 ||
+ strcmp (method, "ListQueuedOwners") == 0)
+ return HANDLE_VALIDATE_OWN;
+
+ if (strcmp (method, "NameHasOwner") == 0)
+ return HANDLE_FILTER_HAS_OWNER_REPLY;
+
+ if (strcmp (method, "GetNameOwner") == 0)
+ return HANDLE_FILTER_GET_OWNER_REPLY;
+
+ if (strcmp (method, "GetConnectionUnixProcessID") == 0 ||
+ strcmp (method, "GetConnectionCredentials") == 0 ||
+ strcmp (method, "GetAdtAuditSessionData") == 0 ||
+ strcmp (method, "GetConnectionSELinuxSecurityContext") == 0 ||
+ strcmp (method, "GetConnectionUnixUser") == 0)
+ return HANDLE_VALIDATE_SEE;
+
+ if (strcmp (method, "StartServiceByName") == 0)
+ return HANDLE_VALIDATE_TALK;
+
+ if (strcmp (method, "ListNames") == 0 ||
+ strcmp (method, "ListActivatableNames") == 0)
+ return HANDLE_FILTER_NAME_LIST_REPLY;
+
+ g_warning ("Unknown bus method %s\n", method);
+ return HANDLE_DENY;
+}
+
+static XdgAppPolicy
+policy_from_handler (BusHandler handler)
+{
+ switch (handler)
+ {
+ case HANDLE_VALIDATE_OWN:
+ return XDG_APP_POLICY_OWN;
+ case HANDLE_VALIDATE_TALK:
+ return XDG_APP_POLICY_TALK;
+ case HANDLE_VALIDATE_SEE:
+ return XDG_APP_POLICY_SEE;
+ default:
+ return XDG_APP_POLICY_NONE;
+ }
+}
+
+static char *
+get_arg0_string (Buffer *buffer)
+{
+ GDBusMessage *message = g_dbus_message_new_from_blob (buffer->data, buffer->size, 0, NULL);
+ GVariant *body, *arg0;
+ char *name = NULL;
+
+ if (message != NULL &&
+ (body = g_dbus_message_get_body (message)) != NULL &&
+ (arg0 = g_variant_get_child_value (body, 0)) != NULL &&
+ g_variant_is_of_type (arg0, G_VARIANT_TYPE_STRING))
+ {
+ name = g_variant_dup_string (arg0, NULL);
+ }
+
+ g_object_unref (message);
+
+ return name;
+}
+
+static gboolean
+validate_arg0_name (XdgAppProxyClient *client, Buffer *buffer, XdgAppPolicy required_policy, XdgAppPolicy *has_policy)
+{
+ GDBusMessage *message = g_dbus_message_new_from_blob (buffer->data, buffer->size, 0, NULL);
+ GVariant *body, *arg0;
+ const char *name;
+ XdgAppPolicy name_policy;
+ gboolean res = FALSE;
+
+ if (has_policy)
+ *has_policy = XDG_APP_POLICY_NONE;
+
+ if (message != NULL &&
+ (body = g_dbus_message_get_body (message)) != NULL &&
+ (arg0 = g_variant_get_child_value (body, 0)) != NULL &&
+ g_variant_is_of_type (arg0, G_VARIANT_TYPE_STRING))
+ {
+ name = g_variant_get_string (arg0, NULL);
+ name_policy = xdg_app_proxy_client_get_policy (client, name);
+
+ if (has_policy)
+ *has_policy = name_policy;
+
+ if (name_policy >= required_policy)
+ res = TRUE;
+ }
+
+ g_object_unref (message);
+ return res;
+}
+
+static Buffer *
+filter_names_list (XdgAppProxyClient *client, Buffer *buffer)
+{
+ GDBusMessage *message = g_dbus_message_new_from_blob (buffer->data, buffer->size, 0, NULL);
+ GVariant *body, *arg0, *new_names;
+ const gchar **names;
+ int i;
+ GVariantBuilder builder;
+ Buffer *filtered;
+
+ if (message == NULL ||
+ (body = g_dbus_message_get_body (message)) == NULL ||
+ (arg0 = g_variant_get_child_value (body, 0)) == NULL ||
+ !g_variant_is_of_type (arg0, G_VARIANT_TYPE_STRING_ARRAY))
+ return NULL;
+
+ names = g_variant_get_strv (arg0, NULL);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ for (i = 0; names[i] != NULL; i++)
+ {
+ if (xdg_app_proxy_client_get_policy (client, names[i]) >= XDG_APP_POLICY_SEE)
+ g_variant_builder_add (&builder, "s", names[i]);
+ }
+ g_free (names);
+
+ new_names = g_variant_builder_end (&builder);
+ g_dbus_message_set_body (message,
+ g_variant_new_tuple (&new_names, 1));
+
+ filtered = message_to_buffer (message);
+ g_object_unref (message);
+ return filtered;
+}
+
+static gboolean
+message_is_name_owner_changed (XdgAppProxyClient *client, Header *header)
+{
+ if (header->type == G_DBUS_MESSAGE_TYPE_SIGNAL &&
+ g_strcmp0 (header->sender, "org.freedesktop.DBus") == 0 &&
+ g_strcmp0 (header->interface, "org.freedesktop.DBus") == 0 &&
+ g_strcmp0 (header->member, "NameOwnerChanged") == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+should_filter_name_owner_changed (XdgAppProxyClient *client, Buffer *buffer)
+{
+ GDBusMessage *message = g_dbus_message_new_from_blob (buffer->data, buffer->size, 0, NULL);
+ GVariant *body, *arg0, *arg1, *arg2;
+ const gchar *name, *old, *new;
+ gboolean filter = TRUE;
+
+ if (message == NULL ||
+ (body = g_dbus_message_get_body (message)) == NULL ||
+ (arg0 = g_variant_get_child_value (body, 0)) == NULL ||
+ !g_variant_is_of_type (arg0, G_VARIANT_TYPE_STRING) ||
+ (arg1 = g_variant_get_child_value (body, 1)) == NULL ||
+ !g_variant_is_of_type (arg1, G_VARIANT_TYPE_STRING) ||
+ (arg2 = g_variant_get_child_value (body, 2)) == NULL ||
+ !g_variant_is_of_type (arg2, G_VARIANT_TYPE_STRING))
+ return TRUE;
+
+ name = g_variant_get_string (arg0, NULL);
+ old = g_variant_get_string (arg1, NULL);
+ new = g_variant_get_string (arg2, NULL);
+
+ if (xdg_app_proxy_client_get_policy (client, name) >= XDG_APP_POLICY_SEE)
+ {
+ if (name[0] != ':')
+ {
+ if (old[0] != 0)
+ xdg_app_proxy_client_update_unique_id_policy_from_name (client, old, name);
+
+ if (new[0] != 0)
+ xdg_app_proxy_client_update_unique_id_policy_from_name (client, new, name);
+ }
+
+ filter = FALSE;
+ }
+
+ g_object_unref (message);
+
+ return filter;
+}
+
+static GList *
+side_get_n_unix_fds (ProxySide *side, int n_fds)
+{
+ GList *res = NULL;
+
+ while (side->control_messages != NULL)
+ {
+ GSocketControlMessage *control_message = side->control_messages->data;
+
+ if (G_IS_UNIX_FD_MESSAGE (control_message))
+ {
+ GUnixFDMessage *fd_message = G_UNIX_FD_MESSAGE (control_message);
+ GUnixFDList *fd_list = g_unix_fd_message_get_fd_list (fd_message);
+ int len = g_unix_fd_list_get_length (fd_list);
+
+ /* I believe that socket control messages are never merged, and
+ the sender side sends only one unix-fd-list per message, so
+ at this point there should always be one full fd list
+ per requested number of fds */
+ if (len != n_fds)
+ {
+ g_warning ("Not right nr of fds in socket message");
+ return NULL;
+ }
+
+ side->control_messages = g_list_delete_link (side->control_messages, side->control_messages);
+
+ return g_list_append (NULL, control_message);
+ }
+
+ g_object_unref (control_message);
+ side->control_messages = g_list_delete_link (side->control_messages, side->control_messages);
+ }
+
+ return res;
+}
+
+static gboolean
+update_socket_messages (ProxySide *side, Buffer *buffer, Header *header)
+{
+ /* We may accidentally combine multiple control messages into one
+ buffer when we receive (since we can do several recvs), so we
+ keep a list of all we get and then only re-attach the amount
+ specified in the header to the buffer. */
+
+ side->control_messages = g_list_concat (side->control_messages, buffer->control_messages);
+ buffer->control_messages = NULL;
+ if (header->unix_fds > 0)
+ {
+ buffer->control_messages = side_get_n_unix_fds (side, header->unix_fds);
+ if (buffer->control_messages == NULL)
+ {
+ g_warning ("Not enough fds for message");
+ side_closed (side);
+ buffer_free (buffer);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void
+queue_fake_message (XdgAppProxyClient *client, GDBusMessage *message, ExpectedReplyType reply_type)
+{
+ Buffer *buffer;
+
+ client->last_serial++;
+ client->serial_offset++;
+ g_dbus_message_set_serial (message, client->last_serial);
+ buffer = message_to_buffer (message);
+ g_object_unref (message);
+
+ queue_outgoing_buffer (&client->bus_side, buffer);
+ queue_expected_reply (&client->client_side, client->last_serial, reply_type);
+}
+
+/* After the first Hello message we need to synthesize a bunch of messages to synchronize the
+ ownership state for the names in the policy */
+static void
+queue_initial_name_ops (XdgAppProxyClient *client)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ gboolean has_wildcards = FALSE;
+
+ g_hash_table_iter_init (&iter, client->proxy->policy);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *name = key;
+ GDBusMessage *message;
+ GVariant *match;
+
+ if (strcmp (name, "org.freedesktop.DBus") == 0)
+ continue;
+
+ /* AddMatch the name so we get told about ownership changes.
+ Do it before the GetNameOwner to avoid races */
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "AddMatch");
+ match = g_variant_new_printf ("type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='%s'", name);
+ g_dbus_message_set_body (message, g_variant_new_tuple (&match, 1));
+ queue_fake_message (client, message, EXPECTED_REPLY_FILTER);
+
+ if (client->proxy->log_messages)
+ g_print ("C%d: -> org.freedesktop.DBus fake AddMatch for %s\n", client->last_serial, name);
+
+ /* Get the current owner of the name (if any) so we can apply policy to it */
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner");
+ g_dbus_message_set_body (message, g_variant_new ("(s)", name));
+ queue_fake_message (client, message, EXPECTED_REPLY_FAKE_GET_NAME_OWNER);
+ g_hash_table_replace (client->get_owner_reply, GINT_TO_POINTER (client->last_serial), g_strdup (name));
+
+ if (client->proxy->log_messages)
+ g_print ("C%d: -> org.freedesktop.DBus fake GetNameOwner for %s\n", client->last_serial, name);
+ }
+
+ /* Same for wildcard proxies. Only here we don't know the actual names to GetNameOwner for, so we have to
+ list all current names */
+ g_hash_table_iter_init (&iter, client->proxy->wildcard_policy);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *name = key;
+ GDBusMessage *message;
+ GVariant *match;
+
+ has_wildcards = TRUE;
+
+ /* AddMatch the name with arg0namespace so we get told about ownership changes to all subnames.
+ Do it before the GetNameOwner to avoid races */
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "AddMatch");
+ match = g_variant_new_printf ("type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0namespace='%s'", name);
+ g_dbus_message_set_body (message, g_variant_new_tuple (&match, 1));
+ queue_fake_message (client, message, EXPECTED_REPLY_FILTER);
+
+ if (client->proxy->log_messages)
+ g_print ("C%d: -> org.freedesktop.DBus fake AddMatch for %s.*\n", client->last_serial, name);
+ }
+
+ if (has_wildcards)
+ {
+ GDBusMessage *message;
+
+ /* AddMatch the name so we get told about ownership changes.
+ Do it before the GetNameOwner to avoid races */
+ message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames");
+ g_dbus_message_set_body (message, g_variant_new ("()"));
+ queue_fake_message (client, message, EXPECTED_REPLY_FAKE_LIST_NAMES);
+
+ if (client->proxy->log_messages)
+ g_print ("C%d: -> org.freedesktop.DBus fake ListNames\n", client->last_serial);
+
+ /* Stop reading from the client, to avoid incomming messages fighting with the ListNames roundtrip.
+ We will start it again once we have handled the ListNames reply */
+ stop_reading (&client->client_side);
+ }
+}
+
+static void
+queue_wildcard_initial_name_ops (XdgAppProxyClient *client, Header *header, Buffer *buffer)
+{
+ GDBusMessage *decoded_message = g_dbus_message_new_from_blob (buffer->data, buffer->size, 0, NULL);
+ GVariant *body, *arg0;
+
+ if (decoded_message != NULL &&
+ header->type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN &&
+ (body = g_dbus_message_get_body (decoded_message)) != NULL &&
+ (arg0 = g_variant_get_child_value (body, 0)) != NULL &&
+ g_variant_is_of_type (arg0, G_VARIANT_TYPE_STRING_ARRAY))
+ {
+ const gchar **names = g_variant_get_strv (arg0, NULL);
+ int i;
+
+ /* Loop over all current names and get the owner for all the ones that match our wildcard
+ policies so that we can update the unique id policies for those */
+ for (i = 0; names[i] != NULL; i++)
+ {
+ const char *name = names[i];
+
+ if (name[0] != ':' &&
+ xdg_app_proxy_get_wildcard_policy (client->proxy, name) != XDG_APP_POLICY_NONE)
+ {
+ /* Get the current owner of the name (if any) so we can apply policy to it */
+ GDBusMessage *message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner");
+ g_dbus_message_set_body (message, g_variant_new ("(s)", name));
+ queue_fake_message (client, message, EXPECTED_REPLY_FAKE_GET_NAME_OWNER);
+ g_hash_table_replace (client->get_owner_reply, GINT_TO_POINTER (client->last_serial), g_strdup (name));
+
+ if (client->proxy->log_messages)
+ g_print ("C%d: -> org.freedesktop.DBus fake GetNameOwner for %s\n", client->last_serial, name);
+ }
+ }
+ g_free (names);
+ }
+
+ g_object_unref (decoded_message);
+}
+
+
+static void
+got_buffer_from_client (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer)
+{
+ ExpectedReplyType expecting_reply = EXPECTED_REPLY_NONE;
+
+ if (client->authenticated && client->proxy->filter)
+ {
+ Header header;
+ BusHandler handler;
+
+ /* Filter and rewrite outgoing messages as needed */
+
+ if (!parse_header (buffer, &header, client->serial_offset, 0, 0))
+ {
+ g_warning ("Invalid message header format");
+ side_closed (side);
+ buffer_free (buffer);
+ return;
+ }
+
+ if (!update_socket_messages (side, buffer, &header))
+ return;
+
+ /* Make sure the client is not playing games with the serials, as that
+ could confuse us. */
+ if (header.serial <= client->last_serial)
+ {
+ g_warning ("Invalid client serial");
+ side_closed (side);
+ buffer_free (buffer);
+ return;
+ }
+ client->last_serial = header.serial;
+
+ if (client->proxy->log_messages)
+ print_outgoing_header (&header);
+
+ /* Keep track of the initial Hello request so that we can read
+ the reply which has our assigned unique id */
+ if (is_dbus_method_call (&header) &&
+ g_strcmp0 (header.member, "Hello") == 0)
+ {
+ expecting_reply = EXPECTED_REPLY_HELLO;
+ client->hello_serial = header.serial;
+ }
+
+ handler = get_dbus_method_handler (client, &header);
+
+ switch (handler)
+ {
+ case HANDLE_FILTER_HAS_OWNER_REPLY:
+ case HANDLE_FILTER_GET_OWNER_REPLY:
+ if (!validate_arg0_name (client, buffer, XDG_APP_POLICY_SEE, NULL))
+ {
+ g_clear_pointer (&buffer, buffer_free);
+ if (handler == HANDLE_FILTER_GET_OWNER_REPLY)
+ buffer = get_error_for_roundtrip (client, &header,
+ "org.freedesktop.DBus.Error.NameHasNoOwner");
+ else
+ buffer = get_bool_reply_for_roundtrip (client, &header, FALSE);
+
+ expecting_reply = EXPECTED_REPLY_REWRITE;
+ break;
+ }
+
+ goto handle_pass;
+
+ case HANDLE_VALIDATE_OWN:
+ case HANDLE_VALIDATE_SEE:
+ case HANDLE_VALIDATE_TALK:
+ {
+ XdgAppPolicy name_policy;
+ if (validate_arg0_name (client, buffer, policy_from_handler (handler), &name_policy))
+ goto handle_pass;
+
+ if (name_policy < (int)HANDLE_VALIDATE_SEE)
+ goto handle_hide;
+ else
+ goto handle_deny;
+ }
+
+ case HANDLE_FILTER_NAME_LIST_REPLY:
+ expecting_reply = EXPECTED_REPLY_LIST_NAMES;
+ goto handle_pass;
+
+ case HANDLE_PASS:
+ handle_pass:
+ if (client_message_generates_reply (&header))
+ {
+ if (expecting_reply == EXPECTED_REPLY_NONE)
+ expecting_reply = EXPECTED_REPLY_NORMAL;
+ }
+
+ break;
+
+ case HANDLE_HIDE:
+ handle_hide:
+ g_clear_pointer (&buffer, buffer_free);
+
+ if (client_message_generates_reply (&header))
+ {
+ const char *error;
+
+ if (client->proxy->log_messages)
+ g_print ("*HIDDEN* (ping)\n");
+
+ if ((header.destination != NULL && header.destination[0] == ':') ||
+ (header.flags & G_DBUS_MESSAGE_FLAGS_NO_AUTO_START) != 0)
+ error = "org.freedesktop.DBus.Error.NameHasNoOwner";
+ else
+ error = "org.freedesktop.DBus.Error.ServiceUnknown";
+
+ buffer = get_error_for_roundtrip (client, &header, error);
+ expecting_reply = EXPECTED_REPLY_REWRITE;
+ }
+ else
+ {
+ if (client->proxy->log_messages)
+ g_print ("*HIDDEN*\n");
+ }
+ break;
+
+ default:
+ case HANDLE_DENY:
+ handle_deny:
+ g_clear_pointer (&buffer, buffer_free);
+
+ if (client_message_generates_reply (&header))
+ {
+ if (client->proxy->log_messages)
+ g_print ("*DENIED* (ping)\n");
+
+ buffer = get_error_for_roundtrip (client, &header,
+ "org.freedesktop.DBus.Error.AccessDenied");
+ expecting_reply = EXPECTED_REPLY_REWRITE;
+ }
+ else
+ {
+ if (client->proxy->log_messages)
+ g_print ("*DENIED*\n");
+ }
+ break;
+ }
+
+ if (buffer != NULL && expecting_reply != EXPECTED_REPLY_NONE)
+ queue_expected_reply (side, header.serial, expecting_reply);
+ }
+
+ if (buffer)
+ queue_outgoing_buffer (&client->bus_side, buffer);
+
+ if (buffer != NULL && expecting_reply == EXPECTED_REPLY_HELLO)
+ queue_initial_name_ops (client);
+}
+
+static void
+got_buffer_from_bus (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer)
+{
+ if (client->authenticated && client->proxy->filter)
+ {
+ Header header;
+ GDBusMessage *rewritten;
+ XdgAppPolicy policy;
+ ExpectedReplyType expected_reply;
+
+ /* Filter and rewrite incomming messages as needed */
+
+ if (!parse_header (buffer, &header, 0, client->serial_offset, client->hello_serial))
+ {
+ g_warning ("Invalid message header format");
+ buffer_free (buffer);
+ side_closed (side);
+ return;
+ }
+
+ if (!update_socket_messages (side, buffer, &header))
+ return;
+
+ if (client->proxy->log_messages)
+ print_incoming_header (&header);
+
+ if (header.has_reply_serial)
+ {
+ expected_reply = steal_expected_reply (get_other_side (side), header.reply_serial);
+
+ /* We only allow replies we expect */
+ if (expected_reply == EXPECTED_REPLY_NONE)
+ {
+ if (client->proxy->log_messages)
+ g_print ("*Unexpected reply*\n");
+ buffer_free (buffer);
+ return;
+ }
+
+ switch (expected_reply)
+ {
+ case EXPECTED_REPLY_HELLO:
+ /* When we get the initial reply to Hello, allow all
+ further communications to our own unique id. */
+ if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ {
+ char *my_id = get_arg0_string (buffer);
+ xdg_app_proxy_client_update_unique_id_policy (client, my_id, XDG_APP_POLICY_TALK);
+ break;
+ }
+
+ case EXPECTED_REPLY_REWRITE:
+ /* Replace a roundtrip ping with the rewritten message */
+
+ rewritten = g_hash_table_lookup (client->rewrite_reply,
+ GINT_TO_POINTER (header.reply_serial));
+
+ if (client->proxy->log_messages)
+ g_print ("*REWRITTEN*\n");
+
+ g_dbus_message_set_serial (rewritten, header.serial);
+ g_clear_pointer (&buffer, buffer_free);
+ buffer = message_to_buffer (rewritten);
+
+ g_hash_table_remove (client->rewrite_reply,
+ GINT_TO_POINTER (header.reply_serial));
+ break;
+
+ case EXPECTED_REPLY_FAKE_LIST_NAMES:
+ /* This is a reply from the bus to a fake ListNames
+ request, request ownership of any name matching a
+ wildcard policy */
+
+ queue_wildcard_initial_name_ops (client, &header, buffer);
+
+ /* Don't forward fake replies to the app */
+ if (client->proxy->log_messages)
+ g_print ("*SKIPPED*\n");
+ g_clear_pointer (&buffer, buffer_free);
+
+ /* Start reading the clients requests now that we are done with the names */
+ start_reading (&client->client_side);
+ break;
+
+ case EXPECTED_REPLY_FAKE_GET_NAME_OWNER:
+ /* This is a reply from the bus to a fake GetNameOwner
+ request, update the policy for this unique name based on
+ the policy */
+ {
+ char *requested_name = g_hash_table_lookup (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial));
+
+ if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ {
+ char *owner = get_arg0_string (buffer);
+ xdg_app_proxy_client_update_unique_id_policy_from_name (client, owner, requested_name);
+ g_free (owner);
+ }
+
+ g_hash_table_remove (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial));
+
+ /* Don't forward fake replies to the app */
+ if (client->proxy->log_messages)
+ g_print ("*SKIPPED*\n");
+ g_clear_pointer (&buffer, buffer_free);
+ break;
+ }
+
+ case EXPECTED_REPLY_FILTER:
+ if (client->proxy->log_messages)
+ g_print ("*SKIPPED*\n");
+ g_clear_pointer (&buffer, buffer_free);
+ break;
+
+ case EXPECTED_REPLY_LIST_NAMES:
+ /* This is a reply from the bus to a ListNames request, filter
+ it according to the policy */
+ if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ {
+ Buffer *filtered_buffer;
+
+ filtered_buffer = filter_names_list (client, buffer);
+ g_clear_pointer (&buffer, buffer_free);
+ buffer = filtered_buffer;
+ }
+
+ break;
+
+ case EXPECTED_REPLY_NORMAL:
+ break;
+
+ default:
+ g_warning ("Unexpected expected reply type %d\n", expected_reply);
+ }
+ }
+ else /* Not reply */
+ {
+
+ /* Don't allow reply types with no reply_serial */
+ if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN ||
+ header.type == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ if (client->proxy->log_messages)
+ g_print ("*Invalid reply*\n");
+ g_clear_pointer (&buffer, buffer_free);
+ }
+
+ /* We filter all NameOwnerChanged signal according to the policy */
+ if (message_is_name_owner_changed (client, &header))
+ {
+ if (should_filter_name_owner_changed (client, buffer))
+ g_clear_pointer (&buffer, buffer_free);
+ }
+ }
+
+ /* All incoming broadcast signals are filtered according to policy */
+ if (header.type == G_DBUS_MESSAGE_TYPE_SIGNAL && header.destination == NULL)
+ {
+ policy = xdg_app_proxy_client_get_policy (client, header.sender);
+ if (policy < XDG_APP_POLICY_TALK)
+ {
+ if (client->proxy->log_messages)
+ g_print ("*FILTERED IN*\n");
+ g_clear_pointer (&buffer, buffer_free);
+ }
+ }
+
+ /* We received and forwarded a message from a trusted peer. Make the policy for
+ this unique id SEE so that the client can track its lifetime. */
+ if (buffer && header.sender && header.sender[0] == ':')
+ xdg_app_proxy_client_update_unique_id_policy (client, header.sender, XDG_APP_POLICY_SEE);
+
+ if (buffer && client_message_generates_reply (&header))
+ queue_expected_reply (side, header.serial, EXPECTED_REPLY_NORMAL);
+ }
+
+ if (buffer)
+ queue_outgoing_buffer (&client->client_side, buffer);
+}
+
+static void
+got_buffer_from_side (ProxySide *side, Buffer *buffer)
+{
+ XdgAppProxyClient *client = side->client;
+
+ if (side == &client->client_side)
+ got_buffer_from_client (client, side, buffer);
+ else
+ got_buffer_from_bus (client, side, buffer);
+}
+
+static gssize
+find_auth_end (XdgAppProxyClient *client, Buffer *buffer)
+{
+ guchar *match;
+ int i;
+
+ /* First try to match any leftover at the start */
+ if (client->auth_end_offset > 0)
+ {
+ gsize left = strlen (AUTH_END_STRING) - client->auth_end_offset;
+ gsize to_match = MIN (left, buffer->pos);
+ /* Matched at least up to to_match */
+ if (memcmp (buffer->data, AUTH_END_STRING + client->auth_end_offset, to_match) == 0)
+ {
+ client->auth_end_offset += to_match;
+
+ /* Matched all */
+ if (client->auth_end_offset == strlen (AUTH_END_STRING))
+ return to_match;
+
+ /* Matched to end of buffer */
+ return -1;
+ }
+
+ /* Did not actually match at start */
+ client->auth_end_offset = -1;
+ }
+
+ /* Look for whole match inside buffer */
+ match = memmem (buffer, buffer->pos,
+ AUTH_END_STRING, strlen (AUTH_END_STRING));
+ if (match != NULL)
+ return match - buffer->data + strlen (AUTH_END_STRING);
+
+ /* Record longest prefix match at the end */
+ for (i = MIN (strlen (AUTH_END_STRING) - 1, buffer->pos); i > 0 ; i--)
+ {
+ if (memcmp (buffer->data + buffer->pos - i, AUTH_END_STRING, i) == 0)
+ {
+ client->auth_end_offset = i;
+ break;
+ }
+ }
+
+ return -1;
+}
+
+static gboolean
+side_in_cb (GSocket *socket, GIOCondition condition, gpointer user_data)
+{
+ ProxySide *side = user_data;
+ XdgAppProxyClient *client = side->client;
+ GError *error = NULL;
+ Buffer *buffer;
+ gboolean retval = G_SOURCE_CONTINUE;
+
+ g_object_ref (client);
+
+ while (!side->closed)
+ {
+ if (!side->got_first_byte)
+ buffer = buffer_new (1, NULL);
+ else if (!client->authenticated)
+ buffer = buffer_new (64, NULL);
+ else
+ buffer = side->current_read_buffer;
+
+ if (!buffer_read (side, buffer, socket))
+ break;
+
+ if (!client->authenticated)
+ {
+ if (buffer->pos > 0)
+ {
+ gboolean found_auth_end = FALSE;
+ gsize extra_data;
+
+ buffer->size = buffer->pos;
+ if (!side->got_first_byte)
+ {
+ buffer->send_credentials = TRUE;
+ side->got_first_byte = TRUE;
+ }
+ /* Look for end of authentication mechanism */
+ else if (side == &client->client_side)
+ {
+ gssize auth_end = find_auth_end (client, buffer);
+
+ if (auth_end >= 0)
+ {
+ found_auth_end = TRUE;
+ buffer->size = auth_end;
+ extra_data = buffer->pos - buffer->size;
+
+ /* We may have gotten some extra data which is not part of
+ the auth handshake, keep it for the next iteration. */
+ if (extra_data > 0)
+ side->extra_input_data = g_bytes_new (buffer->data + buffer->size, extra_data);
+ }
+ }
+
+ got_buffer_from_side (side, buffer);
+
+ if (found_auth_end)
+ client->authenticated = TRUE;
+ }
+ else
+ buffer_free (buffer);
+ }
+ else if (buffer->pos == buffer->size)
+ {
+ if (buffer == &side->header_buffer)
+ {
+ gssize required;
+ required = g_dbus_message_bytes_needed (buffer->data, buffer->size, &error);
+ if (required < 0)
+ {
+ g_warning ("Invalid message header read");
+ side_closed (side);
+ }
+ else
+ side->current_read_buffer = buffer_new (required, buffer);
+ }
+ else
+ {
+ got_buffer_from_side (side, buffer);
+ side->header_buffer.pos = 0;
+ side->current_read_buffer = &side->header_buffer;
+ }
+ }
+ }
+
+ if (side->closed)
+ {
+ side->in_source = NULL;
+ retval = G_SOURCE_REMOVE;
+ }
+
+ g_object_unref (client);
+
+ return retval;
+}
+
+static void
+start_reading (ProxySide *side)
+{
+ GSocket *socket;
+
+ socket = g_socket_connection_get_socket (side->connection);
+ side->in_source = g_socket_create_source (socket, G_IO_IN, NULL);
+ g_source_set_callback (side->in_source, (GSourceFunc)side_in_cb, side, NULL);
+ g_source_attach (side->in_source, NULL);
+ g_source_unref (side->in_source);
+}
+
+static void
+stop_reading (ProxySide *side)
+{
+ if (side->in_source)
+ {
+ g_source_destroy (side->in_source);
+ side->in_source = NULL;
+ }
+}
+
+
+static void
+client_connected_to_dbus (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ XdgAppProxyClient *client = user_data;
+ GSocketConnection *connection;
+ GError *error = NULL;
+ GIOStream *stream;
+
+ stream = g_dbus_address_get_stream_finish (res, NULL, &error);
+ if (stream == NULL)
+ {
+ g_warning ("Failed to connect to bus: %s\n", error->message);
+ g_object_unref (client);
+ return;
+ }
+
+ connection = G_SOCKET_CONNECTION (stream);
+ g_socket_set_blocking (g_socket_connection_get_socket (connection), FALSE);
+ client->bus_side.connection = connection;
+
+ start_reading (&client->client_side);
+ start_reading (&client->bus_side);
+}
+
+static gboolean
+xdg_app_proxy_incoming (GSocketService *service,
+ GSocketConnection *connection,
+ GObject *source_object)
+{
+ XdgAppProxy *proxy = XDG_APP_PROXY (service);
+ XdgAppProxyClient *client;
+
+ client = xdg_app_proxy_client_new (proxy, connection);
+
+ g_dbus_address_get_stream (proxy->dbus_address,
+ NULL,
+ client_connected_to_dbus,
+ client);
+ return TRUE;
+}
+
+static void
+xdg_app_proxy_init (XdgAppProxy *proxy)
+{
+ proxy->policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ proxy->wildcard_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ xdg_app_proxy_add_policy (proxy, "org.freedesktop.DBus", XDG_APP_POLICY_TALK);
+}
+
+static void
+xdg_app_proxy_class_init (XdgAppProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GSocketServiceClass *socket_service_class = G_SOCKET_SERVICE_CLASS (klass);
+
+ object_class->get_property = xdg_app_proxy_get_property;
+ object_class->set_property = xdg_app_proxy_set_property;
+ object_class->finalize = xdg_app_proxy_finalize;
+
+ socket_service_class->incoming = xdg_app_proxy_incoming;
+
+ g_object_class_install_property (object_class,
+ PROP_DBUS_ADDRESS,
+ g_param_spec_string ("dbus-address",
+ "",
+ "",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_SOCKET_PATH,
+ g_param_spec_string ("socket-path",
+ "",
+ "",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+XdgAppProxy *
+xdg_app_proxy_new (const char *dbus_address,
+ const char *socket_path)
+{
+ XdgAppProxy *proxy;
+
+ proxy = g_object_new (XDG_APP_TYPE_PROXY, "dbus-address", dbus_address, "socket-path", socket_path, NULL);
+ return proxy;
+}
+
+gboolean
+xdg_app_proxy_start (XdgAppProxy *proxy, GError **error)
+{
+ GSocketAddress *address;
+ gboolean res;
+
+ unlink (proxy->socket_path);
+
+ address = g_unix_socket_address_new (proxy->socket_path);
+
+ error = NULL;
+ res = g_socket_listener_add_address (G_SOCKET_LISTENER (proxy),
+ address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, /* source_object */
+ NULL, /* effective_address */
+ error);
+ g_object_unref (address);
+
+ if (!res)
+ return FALSE;
+
+
+ g_socket_service_start (G_SOCKET_SERVICE (proxy));
+ return TRUE;
+}
+
+void
+xdg_app_proxy_stop (XdgAppProxy *proxy)
+{
+ unlink (proxy->socket_path);
+
+ g_socket_service_stop (G_SOCKET_SERVICE (proxy));
+}
diff --git a/dbus-proxy/xdg-app-proxy.h b/dbus-proxy/xdg-app-proxy.h
new file mode 100644
index 0000000..3496625
--- /dev/null
+++ b/dbus-proxy/xdg-app-proxy.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2015 Red Hat, Inc
+ *
+ * This program 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 License, 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef __XDG_APP_PROXY_H__
+#define __XDG_APP_PROXY_H__
+
+#include <gio/gio.h>
+#include "libglnx/libglnx.h"
+
+typedef enum {
+ XDG_APP_POLICY_NONE,
+ XDG_APP_POLICY_SEE,
+ XDG_APP_POLICY_TALK,
+ XDG_APP_POLICY_OWN
+} XdgAppPolicy;
+
+typedef struct XdgAppProxy XdgAppProxy;
+
+#define XDG_APP_TYPE_PROXY xdg_app_proxy_get_type()
+#define XDG_APP_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), XDG_APP_TYPE_PROXY, XdgAppProxy))
+#define XDG_APP_IS_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), XDG_APP_TYPE_PROXY))
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdgAppProxy, g_object_unref)
+
+GType xdg_app_proxy_get_type (void);
+
+XdgAppProxy *xdg_app_proxy_new (const char *dbus_address,
+ const char *socket_path);
+void xdg_app_proxy_set_log_messages (XdgAppProxy *proxy,
+ gboolean log);
+void xdg_app_proxy_set_filter (XdgAppProxy *proxy,
+ gboolean filter);
+void xdg_app_proxy_add_policy (XdgAppProxy *proxy,
+ const char *name,
+ XdgAppPolicy policy);
+void xdg_app_proxy_add_wildcarded_policy (XdgAppProxy *proxy,
+ const char *name,
+ XdgAppPolicy policy);
+gboolean xdg_app_proxy_start (XdgAppProxy *proxy,
+ GError **error);
+void xdg_app_proxy_stop (XdgAppProxy *proxy);
+
+#endif /* __XDG_APP_PROXY_H__ */