diff options
author | Alexander Larsson <alexl@redhat.com> | 2015-05-06 12:38:52 +0200 |
---|---|---|
committer | Alexander Larsson <alexl@redhat.com> | 2015-05-06 12:38:52 +0200 |
commit | 7183917583b1356ba0b8edab6a4d86adcd3f4786 (patch) | |
tree | c3330e1fbe1f92138ae4b266937097de763b9e03 | |
parent | c0dca8dee43a6c13f2bdd930359dd67b27c6536e (diff) | |
download | xdg-app-7183917583b1356ba0b8edab6a4d86adcd3f4786.tar.gz |
Break out dbus proxy implementation to separate file
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | dbus-proxy.c | 1840 | ||||
-rw-r--r-- | xdg-app-proxy.c | 1848 | ||||
-rw-r--r-- | xdg-app-proxy.h | 51 |
4 files changed, 1918 insertions, 1824 deletions
diff --git a/Makefile.am b/Makefile.am index 3feaf6c..f4ba632 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,7 @@ include libglnx/Makefile-libglnx.am.inc noinst_LTLIBRARIES = libglnx.la xdg_app_helper_SOURCES = xdg-app-helper.c +xdg_app_helper_LDADD = -L/gnome/lib -lglib-2.0 dbus_built_sources = xdg-app-dbus.c xdg-app-dbus.h systemd_dbus_built_sources = xdg-app-systemd-dbus.c xdg-app-systemd-dbus.h @@ -126,6 +127,8 @@ xdg_app_LDADD = $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) libglnx.la xdg_app_CFLAGS = $(BASE_CFLAGS) $(OSTREE_CFLAGS) $(SOUP_CFLAGS) -I$(srcdir)/libglnx dbus_proxy_SOURCES = \ + xdg-app-proxy.c \ + xdg-app-proxy.h \ dbus-proxy.c \ $(NULL) diff --git a/dbus-proxy.c b/dbus-proxy.c index 041409b..6b4371a 100644 --- a/dbus-proxy.c +++ b/dbus-proxy.c @@ -1,1832 +1,24 @@ -#include <unistd.h> -#include <string.h> - -#include <gio/gio.h> -#include <gio/gunixsocketaddress.h> -#include <gio/gunixconnection.h> -#include <gio/gunixfdmessage.h> - -typedef enum { - XDG_APP_POLICY_NONE, - XDG_APP_POLICY_SEE, - XDG_APP_POLICY_TALK, - XDG_APP_POLICY_OWN -} XdgAppPolicy; - -/** - * Mode of operation - * - * The proxy listens to a unix domain socket, and for each new connection it opens up - * a new connection to the session bus and forwards data between the two. - * During the authentication phase all data is sent, and in the first 1 byte zero we - * also send the proxy credentials to the bus. This means the bus will know the pid - * of the proxy as the pid of the app, unfortunately. +/* + * Copyright © 2015 Red Hat, Inc * - * After authentication we parse incoming dbus messages and do some minor validation - * of the message headers. We then apply the policy to the header which may cause - * us to drop or rewrite messages. + * 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. * - * Each well known name on the bus can have a policy of NONE, SEE, TALK, and OWN. - * NONE means you can't see this name, nor send or receive messages from it. - * SEE means you get told about the existance of this name and can get info about it. - * TALK means you can also send and receive messages to the owner of the name - * OWN means you can also aquire ownership of the name + * 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. * - * Policy is specified on the well known name, but clients are also allowed to - * send directly to the unique id. To handle this we track all outgoing messages - * sent to a well known name, and if we get a reply to it we record unique id that - * replied to the message and apply the policy of the name to that unique id. - * We also parse all NameOwnerChanged signals from the bus and update the policy - * similarly. - * - * This means that each unique id destination effectively gets the - * maximum policy of each of the names it has at one point owned. The fact - * that dropping the name does not lower the policy is unfortunate, but it - * is essentially impossible to avoid this (at least for some time) due to races. + * 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> */ -typedef struct XdgAppProxy XdgAppProxy; -typedef struct XdgAppProxyClient XdgAppProxyClient; - -XdgAppPolicy xdg_app_proxy_get_policy (XdgAppProxy *proxy, const char *name); - -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; - - Buffer *current_read_buffer; - Buffer header_buffer; - - GList *buffers; /* to be sent */ - GList *control_messages; -} ProxySide; - -struct XdgAppProxyClient { - GObject parent; - - XdgAppProxy *proxy; - - gboolean authenticated; - - ProxySide client_side; - ProxySide bus_side; - - /* Filtering data: */ - guint32 hello_serial; - guint32 last_serial; - GHashTable *rewrite_reply; - GHashTable *named_reply; - GHashTable *get_owner_reply; - GHashTable *list_names_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 -}; - -#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)) - -GType xdg_app_proxy_get_type (void); - -#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 -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_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); -} - -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->named_reply); - g_hash_table_destroy (client->get_owner_reply); - g_hash_table_destroy (client->list_names_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; -} - -static void -xdg_app_proxy_client_init (XdgAppProxyClient *client) -{ - init_side (client, &client->client_side); - init_side (client, &client->bus_side); - - client->rewrite_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); - client->named_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); - client->get_owner_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); - client->list_names_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); - 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; - - 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; -} - -XdgAppPolicy -xdg_app_proxy_get_policy (XdgAppProxy *proxy, - const char *name) -{ - guint policy, wildcard_policy; - char *dot; - char buffer[256]; - - policy = GPOINTER_TO_INT (g_hash_table_lookup (proxy->policy, name)); - - 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)); - policy = MAX (policy, wildcard_policy); - } - - return policy; -} - -void -xdg_app_proxy_set_filter (XdgAppProxy *proxy, - gboolean filter) -{ - proxy->filter = filter; -} - -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); - - 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_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; - 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; - 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; - - 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_warning ("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 buffer->pos == buffer->size; -} - -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->pos += res; - return buffer->pos == buffer->size; -} - -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); - - if (side->buffers) - { - Buffer *buffer = side->buffers->data; - - if (buffer_write (side, buffer, socket)) - { - side->buffers = g_list_delete_link (side->buffers, side->buffers); - buffer_free (buffer); - } - } - - 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_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 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 array_len, header_len; - guint32 offset, end_offset; - guint8 header_type; - 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; - 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; - } - - 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_has_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 (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); - 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 (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); - 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 void -queue_error_roundtrip (XdgAppProxyClient *client, Header *header, const char *error_name) -{ - Buffer *ping_buffer = get_ping_buffer_for_header (header); - GDBusMessage *reply; - - queue_outgoing_buffer (&client->bus_side, ping_buffer); - - reply = get_error_for_header (header, error_name); - g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply); -} - - -static void -queue_access_denied_roundtrip (XdgAppProxyClient *client, Header *header) -{ - queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.AccessDenied"); -} - -static void -queue_name_has_no_owner_roundtrip (XdgAppProxyClient *client, Header *header) -{ - queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.NameHasNoOwner"); -} - -static void -queue_service_unknown_roundtrip (XdgAppProxyClient *client, Header *header) -{ - queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.ServiceUnknown"); -} - -static void -queue_bool_reply_roundtrip (XdgAppProxyClient *client, Header *header, gboolean val) -{ - Buffer *ping_buffer = get_ping_buffer_for_header (header); - GDBusMessage *reply; - - queue_outgoing_buffer (&client->bus_side, ping_buffer); - - reply = get_bool_reply_for_header (header, val); - g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply); -} - -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; - - 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 -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 (name[0] != ':' && - xdg_app_proxy_client_get_policy (client, name) > XDG_APP_POLICY_SEE) - { - 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 void -got_buffer_from_client (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer) -{ - if (!client->authenticated) - { - queue_outgoing_buffer (&client->bus_side, buffer); - - if (g_strstr_len ((char *)buffer->data, buffer->size, "BEGIN\r\n") != NULL) - client->authenticated = TRUE; - } - else if (!client->proxy->filter) - { - queue_outgoing_buffer (&client->bus_side, buffer); - } - else - { - Header header; - BusHandler handler; - - /* Filtering */ - - if (!parse_header (buffer, &header)) - { - g_warning ("Invalid message header format"); - side_closed (side); - buffer_free (buffer); - return; - } - - /* We may accidentally combind multiple control messages into one buffer, 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); - } - } - - /* 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); - - if (client->hello_serial == 0 && is_dbus_method_call (&header) && - g_strcmp0 (header.member, "Hello") == 0) - 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)) - { - buffer_free (buffer); - if (handler == HANDLE_FILTER_GET_OWNER_REPLY) - queue_name_has_no_owner_roundtrip (client, &header); - else - queue_bool_reply_roundtrip (client, &header, FALSE); - break; - } - - if (handler == HANDLE_FILTER_GET_OWNER_REPLY) - { - char *name = get_arg0_string (buffer); - g_hash_table_replace (client->get_owner_reply, GINT_TO_POINTER (header.serial), name); - } - - 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: - g_hash_table_replace (client->list_names_reply, GINT_TO_POINTER (header.serial), GINT_TO_POINTER (1)); - goto handle_pass; - - case HANDLE_PASS: - handle_pass: - if (client_message_has_reply (&header) && - header.destination != NULL && - *header.destination != ':') - { - /* Sending to a well known name, track return unique id */ - g_hash_table_replace (client->named_reply, GINT_TO_POINTER (header.serial), g_strdup (header.destination)); - } - - queue_outgoing_buffer (&client->bus_side, buffer); - break; - - case HANDLE_HIDE: - handle_hide: - buffer_free (buffer); - - if (client_message_has_reply (&header)) - { - 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) - queue_name_has_no_owner_roundtrip (client, &header); - else - queue_service_unknown_roundtrip (client, &header); - } - else - { - if (client->proxy->log_messages) - g_print ("*HIDDEN*\n"); - } - break; - - default: - case HANDLE_DENY: - handle_deny: - buffer_free (buffer); - - if (client_message_has_reply (&header)) - { - if (client->proxy->log_messages) - g_print ("*DENIED* (ping)\n"); - - queue_access_denied_roundtrip (client, &header); - } - else - { - if (client->proxy->log_messages) - g_print ("*DENIED*\n"); - } - break; - } - } -} - -static void -got_buffer_from_bus (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer) -{ - if (!client->authenticated) - { - queue_outgoing_buffer (&client->client_side, buffer); - } - else if (!client->proxy->filter) - { - queue_outgoing_buffer (&client->client_side, buffer); - } - else - { - Header header; - GDBusMessage *rewritten; - - /* Filtering */ - - if (!parse_header (buffer, &header)) - { - g_warning ("Invalid message header format"); - side_closed (side); - return; - } - - print_incoming_header (&header); - - if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && - g_strcmp0 (header.sender, "org.freedesktop.DBus") == 0 && - header.has_reply_serial && - client->hello_serial != 0 && - header.reply_serial == client->hello_serial) - { - char *my_id = get_arg0_string (buffer); - xdg_app_proxy_client_update_unique_id_policy (client, my_id, XDG_APP_POLICY_TALK); - } - - if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && - header.sender == NULL && - header.has_reply_serial && - (rewritten = g_hash_table_lookup (client->rewrite_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) - { - Buffer *rewritten_buffer; - g_hash_table_steal (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); - rewritten_buffer = message_to_buffer (rewritten); - g_object_unref (rewritten); - buffer_free (buffer); - queue_outgoing_buffer (&client->client_side, rewritten_buffer); - } - else - { - char *name; - XdgAppPolicy policy; - - if (header.has_reply_serial && - (name = g_hash_table_lookup (client->named_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) - { - if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && - header.sender != NULL && - *header.sender == ':') - { - xdg_app_proxy_client_update_unique_id_policy_from_name (client, header.sender, name); - - g_hash_table_remove (client->named_reply, GINT_TO_POINTER (header.reply_serial)); - } - } - - if (g_strcmp0 (header.sender, "org.freedesktop.DBus") == 0 && - header.has_reply_serial && - (name = g_hash_table_lookup (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) - { - 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, name); - g_free (owner); - } - - g_hash_table_remove (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial)); - } - - policy = xdg_app_proxy_client_get_policy (client, header.sender); - - if (policy >= XDG_APP_POLICY_TALK) - { - /* Filter ListNames replies */ - if (header.has_reply_serial && - g_hash_table_lookup (client->list_names_reply, GINT_TO_POINTER (header.reply_serial))) - { - Buffer *filtered; - g_hash_table_remove (client->list_names_reply, GINT_TO_POINTER (header.reply_serial)); - - filtered = filter_names_list (client, buffer); - buffer_free (buffer); - buffer = filtered; - } - else if (message_is_name_owner_changed (client, &header)) - { - if (filter_name_owner_changed (client, buffer)) - { - buffer_free (buffer); - buffer = NULL; - } - } - - if (buffer) - queue_outgoing_buffer (&client->client_side, buffer); - } - else - { - if (client->proxy->log_messages) - g_print ("*FILTERED IN*\n"); - buffer_free (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 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); - - 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) || !client->authenticated) - { - if (!side->got_first_byte) - { - if (buffer->pos > 0) - { - buffer->send_credentials = TRUE; - buffer->size = buffer->pos; - got_buffer_from_side (side, buffer); - side->got_first_byte = TRUE; - } - else - buffer_free (buffer); - } - else if (!client->authenticated) - { - if (buffer->pos > 0) - { - buffer->size = buffer->pos; - got_buffer_from_side (side, buffer); - } - else - buffer_free (buffer); - } - else 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 -client_connected_to_dbus (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - XdgAppProxyClient *client = user_data; - 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; - } - - client->bus_side.connection = G_SOCKET_CONNECTION (stream); - - 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)); - -} - -XdgAppProxy * -xdg_app_proxy_new (const char *dbus_address) -{ - XdgAppProxy *proxy; - - proxy = g_object_new (XDG_APP_TYPE_PROXY, "dbus-address", dbus_address, NULL); - return proxy; -} - -gboolean -xdg_app_proxy_start (XdgAppProxy *proxy, GError **error) -{ - GSocketAddress *address; - gboolean res; - - proxy->socket_path = g_build_filename (g_get_user_runtime_dir (), "gdbus-proxy", NULL); - unlink (proxy->socket_path); - - g_print ("listening on DBUS_SESSION_BUS_ADDRESS=\"unix:path=%s\"\n", 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; -} +#include "xdg-app-proxy.h" int main (int argc, char *argv[]) @@ -1837,7 +29,7 @@ main (int argc, char *argv[]) proxy = xdg_app_proxy_new (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); - proxy->log_messages = TRUE; + xdg_app_proxy_set_log_messages (proxy, TRUE); xdg_app_proxy_set_filter (proxy, TRUE); xdg_app_proxy_add_policy (proxy, "ca.desrt.dconf", XDG_APP_POLICY_TALK); xdg_app_proxy_add_policy (proxy, "org.gnome.gedit", XDG_APP_POLICY_TALK); diff --git a/xdg-app-proxy.c b/xdg-app-proxy.c new file mode 100644 index 0000000..b4e4f54 --- /dev/null +++ b/xdg-app-proxy.c @@ -0,0 +1,1848 @@ +/* + * 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 <unistd.h> +#include <string.h> + +#include "xdg-app-proxy.h" + +#include <gio/gunixsocketaddress.h> +#include <gio/gunixconnection.h> +#include <gio/gunixfdmessage.h> + +/** + * Mode of operation + * + * The proxy listens to a unix domain socket, and for each new connection it opens up + * a new connection to the session bus and forwards data between the two. + * During the authentication phase all data is sent, and in the first 1 byte zero we + * also send the proxy credentials to the bus. This means the bus will know the pid + * of the proxy as the pid of the app, unfortunately. + * + * After authentication we parse incoming dbus messages and do some minor validation + * of the message headers. We then apply the policy to the header which may cause + * us to drop or rewrite messages. + * + * Each well known name on the bus can have a policy of NONE, SEE, TALK, and OWN. + * NONE means you can't see this name, nor send or receive messages from it. + * SEE means you get told about the existance of this name and can get info about it. + * TALK means you can also send and receive messages to the owner of the name + * OWN means you can also aquire ownership of the name + * + * Policy is specified on the well known name, but clients are also allowed to + * send directly to the unique id. To handle this we track all outgoing messages + * sent to a well known name, and if we get a reply to it we record unique id that + * replied to the message and apply the policy of the name to that unique id. + * We also parse all NameOwnerChanged signals from the bus and update the policy + * similarly. + * + * This means that each unique id destination effectively gets the + * maximum policy of each of the names it has at one point owned. The fact + * that dropping the name does not lower the policy is unfortunate, but it + * is essentially impossible to avoid this (at least for some time) due to races. + * + */ + +typedef struct XdgAppProxyClient XdgAppProxyClient; + +XdgAppPolicy xdg_app_proxy_get_policy (XdgAppProxy *proxy, const char *name); + +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; + + Buffer *current_read_buffer; + Buffer header_buffer; + + GList *buffers; /* to be sent */ + GList *control_messages; +} ProxySide; + +struct XdgAppProxyClient { + GObject parent; + + XdgAppProxy *proxy; + + gboolean authenticated; + + ProxySide client_side; + ProxySide bus_side; + + /* Filtering data: */ + guint32 hello_serial; + guint32 last_serial; + GHashTable *rewrite_reply; + GHashTable *named_reply; + GHashTable *get_owner_reply; + GHashTable *list_names_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 +}; + +#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 +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_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); +} + +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->named_reply); + g_hash_table_destroy (client->get_owner_reply); + g_hash_table_destroy (client->list_names_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; +} + +static void +xdg_app_proxy_client_init (XdgAppProxyClient *client) +{ + init_side (client, &client->client_side); + init_side (client, &client->bus_side); + + client->rewrite_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); + client->named_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + client->get_owner_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + client->list_names_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + 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; + + 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; +} + +XdgAppPolicy +xdg_app_proxy_get_policy (XdgAppProxy *proxy, + const char *name) +{ + guint policy, wildcard_policy; + char *dot; + char buffer[256]; + + policy = GPOINTER_TO_INT (g_hash_table_lookup (proxy->policy, name)); + + 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)); + policy = MAX (policy, wildcard_policy); + } + + return 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); + + 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_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; + 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; + 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; + + 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_warning ("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 buffer->pos == buffer->size; +} + +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->pos += res; + return buffer->pos == buffer->size; +} + +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); + + if (side->buffers) + { + Buffer *buffer = side->buffers->data; + + if (buffer_write (side, buffer, socket)) + { + side->buffers = g_list_delete_link (side->buffers, side->buffers); + buffer_free (buffer); + } + } + + 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_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 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 array_len, header_len; + guint32 offset, end_offset; + guint8 header_type; + 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; + 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; + } + + 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_has_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 (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); + 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 (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); + 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 void +queue_error_roundtrip (XdgAppProxyClient *client, Header *header, const char *error_name) +{ + Buffer *ping_buffer = get_ping_buffer_for_header (header); + GDBusMessage *reply; + + queue_outgoing_buffer (&client->bus_side, ping_buffer); + + reply = get_error_for_header (header, error_name); + g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply); +} + + +static void +queue_access_denied_roundtrip (XdgAppProxyClient *client, Header *header) +{ + queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.AccessDenied"); +} + +static void +queue_name_has_no_owner_roundtrip (XdgAppProxyClient *client, Header *header) +{ + queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.NameHasNoOwner"); +} + +static void +queue_service_unknown_roundtrip (XdgAppProxyClient *client, Header *header) +{ + queue_error_roundtrip (client, header, "org.freedesktop.DBus.Error.ServiceUnknown"); +} + +static void +queue_bool_reply_roundtrip (XdgAppProxyClient *client, Header *header, gboolean val) +{ + Buffer *ping_buffer = get_ping_buffer_for_header (header); + GDBusMessage *reply; + + queue_outgoing_buffer (&client->bus_side, ping_buffer); + + reply = get_bool_reply_for_header (header, val); + g_hash_table_replace (client->rewrite_reply, GINT_TO_POINTER (header->serial), reply); +} + +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; + + 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 +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 (name[0] != ':' && + xdg_app_proxy_client_get_policy (client, name) > XDG_APP_POLICY_SEE) + { + 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 void +got_buffer_from_client (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer) +{ + if (!client->authenticated) + { + queue_outgoing_buffer (&client->bus_side, buffer); + + if (g_strstr_len ((char *)buffer->data, buffer->size, "BEGIN\r\n") != NULL) + client->authenticated = TRUE; + } + else if (!client->proxy->filter) + { + queue_outgoing_buffer (&client->bus_side, buffer); + } + else + { + Header header; + BusHandler handler; + + /* Filtering */ + + if (!parse_header (buffer, &header)) + { + g_warning ("Invalid message header format"); + side_closed (side); + buffer_free (buffer); + return; + } + + /* We may accidentally combind multiple control messages into one buffer, 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); + } + } + + /* 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); + + if (client->hello_serial == 0 && is_dbus_method_call (&header) && + g_strcmp0 (header.member, "Hello") == 0) + 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)) + { + buffer_free (buffer); + if (handler == HANDLE_FILTER_GET_OWNER_REPLY) + queue_name_has_no_owner_roundtrip (client, &header); + else + queue_bool_reply_roundtrip (client, &header, FALSE); + break; + } + + if (handler == HANDLE_FILTER_GET_OWNER_REPLY) + { + char *name = get_arg0_string (buffer); + g_hash_table_replace (client->get_owner_reply, GINT_TO_POINTER (header.serial), name); + } + + 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: + g_hash_table_replace (client->list_names_reply, GINT_TO_POINTER (header.serial), GINT_TO_POINTER (1)); + goto handle_pass; + + case HANDLE_PASS: + handle_pass: + if (client_message_has_reply (&header) && + header.destination != NULL && + *header.destination != ':') + { + /* Sending to a well known name, track return unique id */ + g_hash_table_replace (client->named_reply, GINT_TO_POINTER (header.serial), g_strdup (header.destination)); + } + + queue_outgoing_buffer (&client->bus_side, buffer); + break; + + case HANDLE_HIDE: + handle_hide: + buffer_free (buffer); + + if (client_message_has_reply (&header)) + { + 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) + queue_name_has_no_owner_roundtrip (client, &header); + else + queue_service_unknown_roundtrip (client, &header); + } + else + { + if (client->proxy->log_messages) + g_print ("*HIDDEN*\n"); + } + break; + + default: + case HANDLE_DENY: + handle_deny: + buffer_free (buffer); + + if (client_message_has_reply (&header)) + { + if (client->proxy->log_messages) + g_print ("*DENIED* (ping)\n"); + + queue_access_denied_roundtrip (client, &header); + } + else + { + if (client->proxy->log_messages) + g_print ("*DENIED*\n"); + } + break; + } + } +} + +static void +got_buffer_from_bus (XdgAppProxyClient *client, ProxySide *side, Buffer *buffer) +{ + if (!client->authenticated) + { + queue_outgoing_buffer (&client->client_side, buffer); + } + else if (!client->proxy->filter) + { + queue_outgoing_buffer (&client->client_side, buffer); + } + else + { + Header header; + GDBusMessage *rewritten; + + /* Filtering */ + + if (!parse_header (buffer, &header)) + { + g_warning ("Invalid message header format"); + side_closed (side); + return; + } + + print_incoming_header (&header); + + if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && + g_strcmp0 (header.sender, "org.freedesktop.DBus") == 0 && + header.has_reply_serial && + client->hello_serial != 0 && + header.reply_serial == client->hello_serial) + { + char *my_id = get_arg0_string (buffer); + xdg_app_proxy_client_update_unique_id_policy (client, my_id, XDG_APP_POLICY_TALK); + } + + if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && + header.sender == NULL && + header.has_reply_serial && + (rewritten = g_hash_table_lookup (client->rewrite_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) + { + Buffer *rewritten_buffer; + g_hash_table_steal (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); + rewritten_buffer = message_to_buffer (rewritten); + g_object_unref (rewritten); + buffer_free (buffer); + queue_outgoing_buffer (&client->client_side, rewritten_buffer); + } + else + { + char *name; + XdgAppPolicy policy; + + if (header.has_reply_serial && + (name = g_hash_table_lookup (client->named_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) + { + if (header.type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN && + header.sender != NULL && + *header.sender == ':') + { + xdg_app_proxy_client_update_unique_id_policy_from_name (client, header.sender, name); + + g_hash_table_remove (client->named_reply, GINT_TO_POINTER (header.reply_serial)); + } + } + + if (g_strcmp0 (header.sender, "org.freedesktop.DBus") == 0 && + header.has_reply_serial && + (name = g_hash_table_lookup (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial))) != NULL) + { + 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, name); + g_free (owner); + } + + g_hash_table_remove (client->get_owner_reply, GINT_TO_POINTER (header.reply_serial)); + } + + policy = xdg_app_proxy_client_get_policy (client, header.sender); + + if (policy >= XDG_APP_POLICY_TALK) + { + /* Filter ListNames replies */ + if (header.has_reply_serial && + g_hash_table_lookup (client->list_names_reply, GINT_TO_POINTER (header.reply_serial))) + { + Buffer *filtered; + g_hash_table_remove (client->list_names_reply, GINT_TO_POINTER (header.reply_serial)); + + filtered = filter_names_list (client, buffer); + buffer_free (buffer); + buffer = filtered; + } + else if (message_is_name_owner_changed (client, &header)) + { + if (filter_name_owner_changed (client, buffer)) + { + buffer_free (buffer); + buffer = NULL; + } + } + + if (buffer) + queue_outgoing_buffer (&client->client_side, buffer); + } + else + { + if (client->proxy->log_messages) + g_print ("*FILTERED IN*\n"); + buffer_free (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 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); + + 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) || !client->authenticated) + { + if (!side->got_first_byte) + { + if (buffer->pos > 0) + { + buffer->send_credentials = TRUE; + buffer->size = buffer->pos; + got_buffer_from_side (side, buffer); + side->got_first_byte = TRUE; + } + else + buffer_free (buffer); + } + else if (!client->authenticated) + { + if (buffer->pos > 0) + { + buffer->size = buffer->pos; + got_buffer_from_side (side, buffer); + } + else + buffer_free (buffer); + } + else 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 +client_connected_to_dbus (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + XdgAppProxyClient *client = user_data; + 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; + } + + client->bus_side.connection = G_SOCKET_CONNECTION (stream); + + 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)); + +} + +XdgAppProxy * +xdg_app_proxy_new (const char *dbus_address) +{ + XdgAppProxy *proxy; + + proxy = g_object_new (XDG_APP_TYPE_PROXY, "dbus-address", dbus_address, NULL); + return proxy; +} + +gboolean +xdg_app_proxy_start (XdgAppProxy *proxy, GError **error) +{ + GSocketAddress *address; + gboolean res; + + proxy->socket_path = g_build_filename (g_get_user_runtime_dir (), "gdbus-proxy", NULL); + unlink (proxy->socket_path); + + g_print ("listening on DBUS_SESSION_BUS_ADDRESS=\"unix:path=%s\"\n", 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; +} diff --git a/xdg-app-proxy.h b/xdg-app-proxy.h new file mode 100644 index 0000000..abe96b4 --- /dev/null +++ b/xdg-app-proxy.h @@ -0,0 +1,51 @@ +/* + * 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> + +typedef enum { + XDG_APP_POLICY_NONE, + XDG_APP_POLICY_SEE, + XDG_APP_POLICY_TALK, + XDG_APP_POLICY_OWN +} XdgAppPolicy; + +typedef struct XdgAppProxy XdgAppProxy; + +GType xdg_app_proxy_get_type (void); + +XdgAppProxy *xdg_app_proxy_new (const char *dbus_address); +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); + +#endif /* __XDG_APP_PROXY_H__ */ |