glib/gio/gdbusdaemon.c
Simon McVittie 7401577074 gdbus: Move protocol constants from gdbusdaemon into gdbusprivate.h
These well-known flags and replies are part of the D-Bus Specification,
and also exist with the same names in libdbus header files.
Moving them into a private header means that unit tests like
gdbus-proxy-threads and gdbus-subscribe don't have to reinvent them.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-05-16 22:52:23 +01:00

1738 lines
42 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2012 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors: Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <gstdio.h>
#include <gio/gio.h>
#include <gio/gunixsocketaddress.h>
#include "gdbusdaemon.h"
#include "gdbusprivate.h"
#include "glibintl.h"
#include "gdbus-daemon-generated.h"
#define IDLE_TIMEOUT_MSEC 3000
struct _GDBusDaemon
{
_GFreedesktopDBusSkeleton parent_instance;
gchar *address;
guint timeout;
gchar *tmpdir;
GDBusServer *server;
gchar *guid;
GHashTable *clients;
GHashTable *names;
guint32 next_major_id;
guint32 next_minor_id;
};
struct _GDBusDaemonClass
{
_GFreedesktopDBusSkeletonClass parent_class;
};
enum {
PROP_0,
PROP_ADDRESS,
};
enum
{
SIGNAL_IDLE_TIMEOUT,
NR_SIGNALS
};
static guint g_dbus_daemon_signals[NR_SIGNALS];
static void initable_iface_init (GInitableIface *initable_iface);
static void g_dbus_daemon_iface_init (_GFreedesktopDBusIface *iface);
#define g_dbus_daemon_get_type _g_dbus_daemon_get_type
G_DEFINE_TYPE_WITH_CODE (GDBusDaemon, g_dbus_daemon, _G_TYPE_FREEDESKTOP_DBUS_SKELETON,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
G_IMPLEMENT_INTERFACE (_G_TYPE_FREEDESKTOP_DBUS, g_dbus_daemon_iface_init))
typedef struct {
GDBusDaemon *daemon;
char *id;
GDBusConnection *connection;
GList *matches;
} Client;
typedef struct {
Client *client;
guint32 flags;
} NameOwner;
typedef struct {
int refcount;
char *name;
GDBusDaemon *daemon;
NameOwner *owner;
GList *queue;
} Name;
enum {
MATCH_ELEMENT_TYPE,
MATCH_ELEMENT_SENDER,
MATCH_ELEMENT_INTERFACE,
MATCH_ELEMENT_MEMBER,
MATCH_ELEMENT_PATH,
MATCH_ELEMENT_PATH_NAMESPACE,
MATCH_ELEMENT_DESTINATION,
MATCH_ELEMENT_ARG0NAMESPACE,
MATCH_ELEMENT_EAVESDROP,
MATCH_ELEMENT_ARGN,
MATCH_ELEMENT_ARGNPATH,
};
typedef struct {
guint16 type;
guint16 arg;
char *value;
} MatchElement;
typedef struct {
gboolean eavesdrop;
GDBusMessageType type;
int n_elements;
MatchElement *elements;
} Match;
static GDBusMessage *filter_function (GDBusConnection *connection,
GDBusMessage *message,
gboolean incoming,
gpointer user_data);
static void connection_closed (GDBusConnection *connection,
gboolean remote_peer_vanished,
GError *error,
Client *client);
static NameOwner *
name_owner_new (Client *client, guint32 flags)
{
NameOwner *owner;
owner = g_new0 (NameOwner, 1);
owner->client = client;
owner->flags = flags;
return owner;
}
static void
name_owner_free (NameOwner *owner)
{
g_free (owner);
}
static Name *
name_new (GDBusDaemon *daemon, const char *str)
{
Name *name;
name = g_new0 (Name, 1);
name->refcount = 1;
name->daemon = daemon;
name->name = g_strdup (str);
g_hash_table_insert (daemon->names, name->name, name);
return name;
}
static Name *
name_ref (Name *name)
{
g_assert (name->refcount > 0);
name->refcount++;
return name;
}
static void
name_unref (Name *name)
{
/* scan-build with clang-17 cant follow the refcounting of `Name` structs
* throughout this file. Probably because there are structures like `NameOwner`
* which cause a ref to be added to a `Name` while they exist, but which dont
* actually have a pointer to the `Name`, so the unref of the `Name` when they
* are freed looks like a double-unref.
*
* So, until the static analysis improves, or we find some way to restructure
* the code, squash the false positive use-after-free or double-unref warnings
* by making this function a no-op to the static analyser. */
#ifndef G_ANALYZER_ANALYZING
g_assert (name->refcount > 0);
if (--name->refcount == 0)
{
g_hash_table_remove (name->daemon->names, name->name);
g_free (name->name);
g_free (name);
}
#endif
}
static Name *
name_ensure (GDBusDaemon *daemon, const char *str)
{
Name *name;
name = g_hash_table_lookup (daemon->names, str);
if (name != NULL)
return name_ref (name);
return name_new (daemon, str);
}
static Name *
name_lookup (GDBusDaemon *daemon, const char *str)
{
return g_hash_table_lookup (daemon->names, str);
}
static gboolean
is_key (const char *key_start, const char *key_end, const char *value)
{
gsize len = strlen (value);
g_assert (key_end >= key_start);
if (len != (gsize) (key_end - key_start))
return FALSE;
return strncmp (key_start, value, len) == 0;
}
static gboolean
parse_key (MatchElement *element, const char *key_start, const char *key_end)
{
gboolean res = TRUE;
if (is_key (key_start, key_end, "type"))
{
element->type = MATCH_ELEMENT_TYPE;
}
else if (is_key (key_start, key_end, "sender"))
{
element->type = MATCH_ELEMENT_SENDER;
}
else if (is_key (key_start, key_end, "interface"))
{
element->type = MATCH_ELEMENT_INTERFACE;
}
else if (is_key (key_start, key_end, "member"))
{
element->type = MATCH_ELEMENT_MEMBER;
}
else if (is_key (key_start, key_end, "path"))
{
element->type = MATCH_ELEMENT_PATH;
}
else if (is_key (key_start, key_end, "path_namespace"))
{
element->type = MATCH_ELEMENT_PATH_NAMESPACE;
}
else if (is_key (key_start, key_end, "destination"))
{
element->type = MATCH_ELEMENT_DESTINATION;
}
else if (is_key (key_start, key_end, "arg0namespace"))
{
element->type = MATCH_ELEMENT_ARG0NAMESPACE;
}
else if (is_key (key_start, key_end, "eavesdrop"))
{
element->type = MATCH_ELEMENT_EAVESDROP;
}
else if (key_end - key_start > 3 && is_key (key_start, key_start + 3, "arg"))
{
const char *digits = key_start + 3;
const char *end_digits = digits;
while (end_digits < key_end && g_ascii_isdigit (*end_digits))
end_digits++;
if (end_digits == key_end) /* argN */
{
element->type = MATCH_ELEMENT_ARGN;
element->arg = atoi (digits);
}
else if (is_key (end_digits, key_end, "path")) /* argNpath */
{
element->type = MATCH_ELEMENT_ARGNPATH;
element->arg = atoi (digits);
}
else
res = FALSE;
}
else
res = FALSE;
return res;
}
static const char *
parse_value (MatchElement *element, const char *s)
{
char quote_char;
GString *value;
value = g_string_new ("");
quote_char = 0;
for (;*s; s++)
{
if (quote_char == 0)
{
switch (*s)
{
case '\'':
quote_char = '\'';
break;
case ',':
s++;
goto out;
case '\\':
quote_char = '\\';
break;
default:
g_string_append_c (value, *s);
break;
}
}
else if (quote_char == '\\')
{
/* \ only counts as an escape if escaping a quote mark */
if (*s != '\'')
g_string_append_c (value, '\\');
g_string_append_c (value, *s);
quote_char = 0;
}
else /* quote_char == ' */
{
if (*s == '\'')
quote_char = 0;
else
g_string_append_c (value, *s);
}
}
out:
if (quote_char == '\\')
g_string_append_c (value, '\\');
else if (quote_char == '\'')
{
g_string_free (value, TRUE);
return NULL;
}
element->value = g_string_free (value, FALSE);
return s;
}
static Match *
match_new (const char *str)
{
Match *match;
GArray *elements;
const char *p;
const char *key_start;
const char *key_end;
MatchElement element;
gboolean eavesdrop;
GDBusMessageType type;
gsize i;
eavesdrop = FALSE;
type = G_DBUS_MESSAGE_TYPE_INVALID;
elements = g_array_new (TRUE, TRUE, sizeof (MatchElement));
p = str;
while (*p != 0)
{
memset (&element, 0, sizeof (element));
/* Skip initial whitespace */
while (*p && g_ascii_isspace (*p))
p++;
key_start = p;
/* Read non-whitespace non-equals chars */
while (*p && *p != '=' && !g_ascii_isspace (*p))
p++;
key_end = p;
/* Skip any whitespace after key */
while (*p && g_ascii_isspace (*p))
p++;
if (key_start == key_end)
continue; /* Allow trailing whitespace */
if (*p != '=')
goto error;
++p;
if (!parse_key (&element, key_start, key_end))
goto error;
p = parse_value (&element, p);
if (p == NULL)
goto error;
if (element.type == MATCH_ELEMENT_EAVESDROP)
{
if (strcmp (element.value, "true") == 0)
eavesdrop = TRUE;
else if (strcmp (element.value, "false") == 0)
eavesdrop = FALSE;
else
{
g_free (element.value);
goto error;
}
g_free (element.value);
}
else if (element.type == MATCH_ELEMENT_TYPE)
{
if (strcmp (element.value, "signal") == 0)
type = G_DBUS_MESSAGE_TYPE_SIGNAL;
else if (strcmp (element.value, "method_call") == 0)
type = G_DBUS_MESSAGE_TYPE_METHOD_CALL;
else if (strcmp (element.value, "method_return") == 0)
type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN;
else if (strcmp (element.value, "error") == 0)
type = G_DBUS_MESSAGE_TYPE_ERROR;
else
{
g_free (element.value);
goto error;
}
g_free (element.value);
}
else
g_array_append_val (elements, element);
}
match = g_new0 (Match, 1);
match->n_elements = elements->len;
match->elements = (MatchElement *)g_array_free (elements, FALSE);
match->eavesdrop = eavesdrop;
match->type = type;
return match;
error:
for (i = 0; i < elements->len; i++)
g_free (g_array_index (elements, MatchElement, i).value);
g_array_free (elements, TRUE);
return NULL;
}
static void
match_free (Match *match)
{
int i;
for (i = 0; i < match->n_elements; i++)
g_free (match->elements[i].value);
g_free (match->elements);
g_free (match);
}
static gboolean
match_equal (Match *a, Match *b)
{
int i;
if (a->eavesdrop != b->eavesdrop)
return FALSE;
if (a->type != b->type)
return FALSE;
if (a->n_elements != b->n_elements)
return FALSE;
for (i = 0; i < a->n_elements; i++)
{
if (a->elements[i].type != b->elements[i].type ||
a->elements[i].arg != b->elements[i].arg ||
strcmp (a->elements[i].value, b->elements[i].value) != 0)
return FALSE;
}
return TRUE;
}
static const gchar *
message_get_argN (GDBusMessage *message, int n, gboolean allow_path)
{
const gchar *ret;
GVariant *body;
ret = NULL;
body = g_dbus_message_get_body (message);
if (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE))
{
GVariant *item;
item = g_variant_get_child_value (body, n);
if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING) ||
(allow_path && g_variant_is_of_type (item, G_VARIANT_TYPE_OBJECT_PATH)))
ret = g_variant_get_string (item, NULL);
g_variant_unref (item);
}
return ret;
}
enum {
CHECK_TYPE_STRING,
CHECK_TYPE_NAME,
CHECK_TYPE_PATH_PREFIX,
CHECK_TYPE_PATH_RELATED,
CHECK_TYPE_NAMESPACE_PREFIX
};
static gboolean
match_matches (GDBusDaemon *daemon,
Match *match, GDBusMessage *message,
gboolean has_destination)
{
MatchElement *element;
Name *name;
int i;
size_t len, len2;
const char *value;
int check_type;
if (has_destination && !match->eavesdrop)
return FALSE;
if (match->type != G_DBUS_MESSAGE_TYPE_INVALID &&
g_dbus_message_get_message_type (message) != match->type)
return FALSE;
for (i = 0; i < match->n_elements; i++)
{
element = &match->elements[i];
check_type = CHECK_TYPE_STRING;
switch (element->type)
{
case MATCH_ELEMENT_SENDER:
check_type = CHECK_TYPE_NAME;
value = g_dbus_message_get_sender (message);
if (value == NULL)
value = DBUS_SERVICE_DBUS;
break;
case MATCH_ELEMENT_DESTINATION:
check_type = CHECK_TYPE_NAME;
value = g_dbus_message_get_destination (message);
break;
case MATCH_ELEMENT_INTERFACE:
value = g_dbus_message_get_interface (message);
break;
case MATCH_ELEMENT_MEMBER:
value = g_dbus_message_get_member (message);
break;
case MATCH_ELEMENT_PATH:
value = g_dbus_message_get_path (message);
break;
case MATCH_ELEMENT_PATH_NAMESPACE:
check_type = CHECK_TYPE_PATH_PREFIX;
value = g_dbus_message_get_path (message);
break;
case MATCH_ELEMENT_ARG0NAMESPACE:
check_type = CHECK_TYPE_NAMESPACE_PREFIX;
value = message_get_argN (message, 0, FALSE);
break;
case MATCH_ELEMENT_ARGN:
value = message_get_argN (message, element->arg, FALSE);
break;
case MATCH_ELEMENT_ARGNPATH:
check_type = CHECK_TYPE_PATH_RELATED;
value = message_get_argN (message, element->arg, TRUE);
break;
default:
case MATCH_ELEMENT_TYPE:
case MATCH_ELEMENT_EAVESDROP:
g_assert_not_reached ();
}
if (value == NULL)
return FALSE;
switch (check_type)
{
case CHECK_TYPE_STRING:
if (strcmp (element->value, value) != 0)
return FALSE;
break;
case CHECK_TYPE_NAME:
name = name_lookup (daemon, element->value);
if (name != NULL && name->owner != NULL)
{
if (strcmp (name->owner->client->id, value) != 0)
return FALSE;
}
else if (strcmp (element->value, value) != 0)
return FALSE;
break;
case CHECK_TYPE_PATH_PREFIX:
len = strlen (element->value);
/* Make sure to handle the case of element->value == '/'. */
if (len == 1)
break;
/* Fail if there's no prefix match, or if the prefix match doesn't
* finish at the end of or at a separator in the @value. */
if (!g_str_has_prefix (value, element->value))
return FALSE;
if (value[len] != 0 && value[len] != '/')
return FALSE;
break;
case CHECK_TYPE_PATH_RELATED:
len = strlen (element->value);
len2 = strlen (value);
if (!(strcmp (value, element->value) == 0 ||
(len2 > 0 && value[len2-1] == '/' && g_str_has_prefix (element->value, value)) ||
(len > 0 && element->value[len-1] == '/' && g_str_has_prefix (value, element->value))))
return FALSE;
break;
case CHECK_TYPE_NAMESPACE_PREFIX:
len = strlen (element->value);
if (!(g_str_has_prefix (value, element->value) &&
(value[len] == 0 || value[len] == '.')))
return FALSE;
break;
default:
g_assert_not_reached ();
}
}
return TRUE;
}
static void
broadcast_message (GDBusDaemon *daemon,
GDBusMessage *message,
gboolean has_destination,
gboolean preserve_serial,
Client *not_to)
{
GList *clients, *l, *ll;
GDBusMessage *copy;
clients = g_hash_table_get_values (daemon->clients);
for (l = clients; l != NULL; l = l->next)
{
Client *client = l->data;
if (client == not_to)
continue;
for (ll = client->matches; ll != NULL; ll = ll->next)
{
Match *match = ll->data;
if (match_matches (daemon, match, message, has_destination))
break;
}
if (ll != NULL)
{
copy = g_dbus_message_copy (message, NULL);
if (copy)
{
g_dbus_connection_send_message (client->connection, copy,
preserve_serial?G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL:0, NULL, NULL);
g_object_unref (copy);
}
}
}
g_list_free (clients);
}
static void
send_name_owner_changed (GDBusDaemon *daemon,
const char *name,
const char *old_owner,
const char *new_owner)
{
GDBusMessage *signal_message;
signal_message = g_dbus_message_new_signal (DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"NameOwnerChanged");
g_dbus_message_set_body (signal_message,
g_variant_new ("(sss)",
name,
old_owner ? old_owner : "",
new_owner ? new_owner : ""));
broadcast_message (daemon, signal_message, FALSE, FALSE, NULL);
g_object_unref (signal_message);
}
static gboolean
name_unqueue_owner (Name *name, Client *client)
{
GList *l;
for (l = name->queue; l != NULL; l = l->next)
{
NameOwner *other = l->data;
if (other->client == client)
{
name->queue = g_list_delete_link (name->queue, l);
name_unref (name);
name_owner_free (other);
return TRUE;
}
}
return FALSE;
}
static void
name_replace_owner (Name *name, NameOwner *owner)
{
GDBusDaemon *daemon = name->daemon;
NameOwner *old_owner;
char *old_name = NULL, *new_name = NULL;
Client *new_client = NULL;
if (owner)
new_client = owner->client;
name_ref (name);
old_owner = name->owner;
if (old_owner)
{
Client *old_client = old_owner->client;
g_assert (old_owner->client != new_client);
g_dbus_connection_emit_signal (old_client->connection,
NULL, DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS, "NameLost",
g_variant_new ("(s)",
name->name), NULL);
old_name = g_strdup (old_client->id);
if (old_owner->flags & DBUS_NAME_FLAG_DO_NOT_QUEUE)
{
name_unref (name);
name_owner_free (old_owner);
}
else
name->queue = g_list_prepend (name->queue, old_owner);
}
name->owner = owner;
if (owner)
{
name_unqueue_owner (name, owner->client);
name_ref (name);
new_name = new_client->id;
g_dbus_connection_emit_signal (new_client->connection,
NULL, DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS, "NameAcquired",
g_variant_new ("(s)",
name->name), NULL);
}
send_name_owner_changed (daemon, name->name, old_name, new_name);
g_free (old_name);
name_unref (name);
}
static void
name_release_owner (Name *name)
{
NameOwner *next_owner = NULL;
name_ref (name);
/* Will someone else take over? */
if (name->queue)
{
next_owner = name->queue->data;
name_unref (name);
name->queue = g_list_delete_link (name->queue, name->queue);
}
name->owner->flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE;
name_replace_owner (name, next_owner);
name_unref (name);
}
static void
name_queue_owner (Name *name, NameOwner *owner)
{
GList *l;
for (l = name->queue; l != NULL; l = l->next)
{
NameOwner *other = l->data;
if (other->client == owner->client)
{
other->flags = owner->flags;
name_owner_free (owner);
return;
}
}
name->queue = g_list_append (name->queue, owner);
name_ref (name);
}
static Client *
client_new (GDBusDaemon *daemon, GDBusConnection *connection)
{
Client *client;
GError *error = NULL;
client = g_new0 (Client, 1);
client->daemon = daemon;
client->id = g_strdup_printf (":%d.%d", daemon->next_major_id, daemon->next_minor_id);
client->connection = g_object_ref (connection);
if (daemon->next_minor_id == G_MAXUINT32)
{
daemon->next_minor_id = 0;
daemon->next_major_id++;
}
else
daemon->next_minor_id++;
g_object_set_data (G_OBJECT (connection), "client", client);
g_hash_table_insert (daemon->clients, client->id, client);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon), connection,
DBUS_PATH_DBUS, &error);
g_assert_no_error (error);
g_signal_connect (connection, "closed", G_CALLBACK (connection_closed), client);
g_dbus_connection_add_filter (connection,
filter_function,
client, NULL);
send_name_owner_changed (daemon, client->id, NULL, client->id);
return client;
}
static void
client_free (Client *client)
{
GDBusDaemon *daemon = client->daemon;
GList *l, *names;
g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (daemon),
client->connection);
g_hash_table_remove (daemon->clients, client->id);
names = g_hash_table_get_values (daemon->names);
for (l = names; l != NULL; l = l->next)
{
Name *name = l->data;
name_ref (name);
if (name->owner && name->owner->client == client)
{
/* Help static analysers with the refcount at this point. */
g_assert (name->refcount >= 2);
name_release_owner (name);
}
name_unqueue_owner (name, client);
name_unref (name);
}
g_list_free (names);
send_name_owner_changed (daemon, client->id, client->id, NULL);
g_object_unref (client->connection);
for (l = client->matches; l != NULL; l = l->next)
match_free (l->data);
g_list_free (client->matches);
g_free (client->id);
g_free (client);
}
static gboolean
idle_timeout_cb (gpointer user_data)
{
GDBusDaemon *daemon = user_data;
daemon->timeout = 0;
g_signal_emit (daemon,
g_dbus_daemon_signals[SIGNAL_IDLE_TIMEOUT],
0);
return G_SOURCE_REMOVE;
}
static void
connection_closed (GDBusConnection *connection,
gboolean remote_peer_vanished,
GError *error,
Client *client)
{
GDBusDaemon *daemon = client->daemon;
client_free (client);
if (g_hash_table_size (daemon->clients) == 0)
daemon->timeout = g_timeout_add (IDLE_TIMEOUT_MSEC,
idle_timeout_cb,
daemon);
}
static gboolean
handle_add_match (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_rule)
{
Client *client = g_object_get_data (G_OBJECT (g_dbus_method_invocation_get_connection (invocation)), "client");
Match *match;
match = match_new (arg_rule);
if (match == NULL)
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_INVALID,
"Invalid rule: %s", arg_rule);
else
{
client->matches = g_list_prepend (client->matches, match);
_g_freedesktop_dbus_complete_add_match (object, invocation);
}
return TRUE;
}
static gboolean
handle_get_connection_selinux_security_context (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN,
"selinux context not supported");
_g_freedesktop_dbus_complete_get_connection_selinux_security_context (object, invocation, "");
return TRUE;
}
static gboolean
handle_get_connection_unix_process_id (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,
"connection pid not supported");
return TRUE;
}
static gboolean
handle_get_connection_unix_user (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,
"connection user not supported");
return TRUE;
}
static gboolean
handle_get_id (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
_g_freedesktop_dbus_complete_get_id (object, invocation,
daemon->guid);
return TRUE;
}
static gboolean
handle_get_name_owner (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
Name *name;
if (strcmp (arg_name, DBUS_SERVICE_DBUS) == 0)
{
_g_freedesktop_dbus_complete_get_name_owner (object, invocation, DBUS_SERVICE_DBUS);
return TRUE;
}
if (arg_name[0] == ':')
{
if (g_hash_table_lookup (daemon->clients, arg_name) == NULL)
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER,
"Could not get owner of name '%s': no such name", arg_name);
else
_g_freedesktop_dbus_complete_get_name_owner (object, invocation, arg_name);
return TRUE;
}
name = name_lookup (daemon, arg_name);
if (name == NULL || name->owner == NULL)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER,
"Could not get owner of name '%s': no such name", arg_name);
return TRUE;
}
_g_freedesktop_dbus_complete_get_name_owner (object, invocation, name->owner->client->id);
return TRUE;
}
static gboolean
handle_hello (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation)
{
Client *client = g_object_get_data (G_OBJECT (g_dbus_method_invocation_get_connection (invocation)), "client");
_g_freedesktop_dbus_complete_hello (object, invocation, client->id);
g_dbus_connection_emit_signal (client->connection,
NULL, DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS, "NameAcquired",
g_variant_new ("(s)",
client->id), NULL);
return TRUE;
}
static gboolean
handle_list_activatable_names (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation)
{
const char *names[] = { NULL };
_g_freedesktop_dbus_complete_list_activatable_names (object,
invocation,
names);
return TRUE;
}
static gboolean
handle_list_names (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
GPtrArray *array;
GPtrArray *clients, *names;
clients = g_hash_table_get_values_as_ptr_array (daemon->clients);
array = g_steal_pointer (&clients);
names = g_hash_table_get_values_as_ptr_array (daemon->names);
g_ptr_array_extend_and_steal (array, g_steal_pointer (&names));
g_ptr_array_add (array, NULL);
_g_freedesktop_dbus_complete_list_names (object,
invocation,
(const gchar * const*)array->pdata);
g_ptr_array_free (array, TRUE);
return TRUE;
}
static gboolean
handle_list_queued_owners (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
GPtrArray *array;
Name *name;
GList *l;
array = g_ptr_array_new ();
name = name_lookup (daemon, arg_name);
if (name && name->owner)
{
for (l = name->queue; l != NULL; l = l->next)
{
Client *client = l->data;
g_ptr_array_add (array, client->id);
}
}
g_ptr_array_add (array, NULL);
_g_freedesktop_dbus_complete_list_queued_owners (object,
invocation,
(const gchar * const*)array->pdata);
g_ptr_array_free (array, TRUE);
return TRUE;
}
static gboolean
handle_name_has_owner (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
Name *name;
Client *client;
name = name_lookup (daemon, arg_name);
client = g_hash_table_lookup (daemon->clients, arg_name);
_g_freedesktop_dbus_complete_name_has_owner (object, invocation,
name != NULL || client != NULL);
return TRUE;
}
static gboolean
handle_release_name (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name)
{
Client *client = g_object_get_data (G_OBJECT (g_dbus_method_invocation_get_connection (invocation)), "client");
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
Name *name;
guint32 result;
if (!g_dbus_is_name (arg_name))
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Given bus name \"%s\" is not valid", arg_name);
return TRUE;
}
if (*arg_name == ':')
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot release a service starting with ':' such as \"%s\"", arg_name);
return TRUE;
}
if (strcmp (arg_name, DBUS_SERVICE_DBUS) == 0)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot release a service named " DBUS_SERVICE_DBUS ", because that is owned by the bus");
return TRUE;
}
name = name_lookup (daemon, arg_name);
if (name == NULL)
result = DBUS_RELEASE_NAME_REPLY_NON_EXISTENT;
else if (name->owner && name->owner->client == client)
{
name_release_owner (name);
result = DBUS_RELEASE_NAME_REPLY_RELEASED;
}
else if (name_unqueue_owner (name, client))
result = DBUS_RELEASE_NAME_REPLY_RELEASED;
else
result = DBUS_RELEASE_NAME_REPLY_NOT_OWNER;
_g_freedesktop_dbus_complete_release_name (object, invocation, result);
return TRUE;
}
static gboolean
handle_reload_config (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation)
{
_g_freedesktop_dbus_complete_reload_config (object, invocation);
return TRUE;
}
static gboolean
handle_update_activation_environment (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
GVariant *arg_environment)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
"UpdateActivationEnvironment not implemented");
return TRUE;
}
static gboolean
handle_remove_match (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_rule)
{
Client *client = g_object_get_data (G_OBJECT (g_dbus_method_invocation_get_connection (invocation)), "client");
Match *match, *other_match;
GList *l;
match = match_new (arg_rule);
if (match == NULL)
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_INVALID,
"Invalid rule: %s", arg_rule);
else
{
for (l = client->matches; l != NULL; l = l->next)
{
other_match = l->data;
if (match_equal (match, other_match))
{
match_free (other_match);
client->matches = g_list_delete_link (client->matches, l);
break;
}
}
if (l == NULL)
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,
"The given match rule wasn't found and can't be removed");
else
_g_freedesktop_dbus_complete_remove_match (object, invocation);
}
if (match)
match_free (match);
return TRUE;
}
static gboolean
handle_request_name (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name,
guint flags)
{
Client *client = g_object_get_data (G_OBJECT (g_dbus_method_invocation_get_connection (invocation)), "client");
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
Name *name;
NameOwner *owner;
guint32 result;
if (!g_dbus_is_name (arg_name))
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Requested bus name \"%s\" is not valid", arg_name);
return TRUE;
}
if (*arg_name == ':')
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot acquire a service starting with ':' such as \"%s\"", arg_name);
return TRUE;
}
if (strcmp (arg_name, DBUS_SERVICE_DBUS) == 0)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot acquire a service named " DBUS_SERVICE_DBUS ", because that is reserved");
return TRUE;
}
name = name_ensure (daemon, arg_name);
if (name->owner == NULL)
{
owner = name_owner_new (client, flags);
name_replace_owner (name, owner);
result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
}
else if (name->owner && name->owner->client == client)
{
name->owner->flags = flags;
result = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER;
}
else if ((flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) &&
(!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) ||
!(name->owner->flags & DBUS_NAME_FLAG_ALLOW_REPLACEMENT)))
{
/* Unqueue if queued */
name_unqueue_owner (name, client);
result = DBUS_REQUEST_NAME_REPLY_EXISTS;
}
else if (!(flags & DBUS_NAME_FLAG_DO_NOT_QUEUE) &&
(!(flags & DBUS_NAME_FLAG_REPLACE_EXISTING) ||
!(name->owner->flags & DBUS_NAME_FLAG_ALLOW_REPLACEMENT)))
{
/* Queue the connection */
owner = name_owner_new (client, flags);
name_queue_owner (name, owner);
result = DBUS_REQUEST_NAME_REPLY_IN_QUEUE;
}
else
{
/* Replace the current owner */
owner = name_owner_new (client, flags);
name_replace_owner (name, owner);
result = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
}
name_unref (name);
_g_freedesktop_dbus_complete_request_name (object, invocation, result);
return TRUE;
}
static gboolean
handle_start_service_by_name (_GFreedesktopDBus *object,
GDBusMethodInvocation *invocation,
const gchar *arg_name,
guint arg_flags)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
Name *name;
name = name_lookup (daemon, arg_name);
if (name)
_g_freedesktop_dbus_complete_start_service_by_name (object, invocation,
DBUS_START_REPLY_ALREADY_RUNNING);
else
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN,
"No support for activation for name: %s", arg_name);
return TRUE;
}
G_GNUC_PRINTF(5, 6)
static void
return_error (Client *client, GDBusMessage *message,
GQuark domain,
gint code,
const gchar *format,
...)
{
GDBusMessage *reply;
va_list var_args;
char *error_message;
GError *error;
gchar *dbus_error_name;
va_start (var_args, format);
error_message = g_strdup_vprintf (format, var_args);
va_end (var_args);
error = g_error_new_literal (domain, code, "");
dbus_error_name = g_dbus_error_encode_gerror (error);
reply = g_dbus_message_new_method_error_literal (message,
dbus_error_name,
error_message);
g_error_free (error);
g_free (dbus_error_name);
g_free (error_message);
if (!g_dbus_connection_send_message (client->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL))
g_warning ("Error sending reply");
g_object_unref (reply);
}
static GDBusMessage *
route_message (Client *source_client, GDBusMessage *message)
{
const char *dest;
Client *dest_client;
GDBusDaemon *daemon;
daemon = source_client->daemon;
dest_client = NULL;
dest = g_dbus_message_get_destination (message);
if (dest != NULL && strcmp (dest, DBUS_SERVICE_DBUS) != 0)
{
dest_client = g_hash_table_lookup (daemon->clients, dest);
if (dest_client == NULL)
{
Name *name;
name = name_lookup (daemon, dest);
if (name && name->owner)
dest_client = name->owner->client;
}
if (dest_client == NULL)
{
if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL)
return_error (source_client, message,
G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN,
"The name %s is unknown", dest);
}
else
{
GError *error = NULL;
if (!g_dbus_connection_send_message (dest_client->connection, message, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, NULL, &error))
{
g_warning ("Error forwarding message: %s", error->message);
g_error_free (error);
}
}
}
broadcast_message (daemon, message, dest_client != NULL, TRUE, dest_client);
/* Swallow messages not for the bus */
if (dest == NULL || strcmp (dest, DBUS_SERVICE_DBUS) != 0)
{
g_object_unref (message);
message = NULL;
}
return message;
}
static GDBusMessage *
copy_if_locked (GDBusMessage *message)
{
if (g_dbus_message_get_locked (message))
{
GDBusMessage *copy = g_dbus_message_copy (message, NULL);
g_object_unref (message);
message = copy;
}
return message;
}
static GDBusMessage *
filter_function (GDBusConnection *connection,
GDBusMessage *message,
gboolean incoming,
gpointer user_data)
{
Client *client = user_data;
if (0)
{
const char *types[] = {"invalid", "method_call", "method_return", "error", "signal" };
g_printerr ("%s%s %s %d(%d) sender: %s destination: %s %s %s.%s\n",
client->id,
incoming? "->" : "<-",
types[g_dbus_message_get_message_type (message)],
g_dbus_message_get_serial (message),
g_dbus_message_get_reply_serial (message),
g_dbus_message_get_sender (message),
g_dbus_message_get_destination (message),
g_dbus_message_get_path (message),
g_dbus_message_get_interface (message),
g_dbus_message_get_member (message));
}
if (incoming)
{
/* Ensure its not locked so we can set the sender */
message = copy_if_locked (message);
if (message == NULL)
{
g_warning ("Failed to copy incoming message");
return NULL;
}
g_dbus_message_set_sender (message, client->id);
return route_message (client, message);
}
else
{
if (g_dbus_message_get_sender (message) == NULL ||
g_dbus_message_get_destination (message) == NULL)
{
message = copy_if_locked (message);
if (message == NULL)
{
g_warning ("Failed to copy outgoing message");
return NULL;
}
}
if (g_dbus_message_get_sender (message) == NULL)
g_dbus_message_set_sender (message, DBUS_SERVICE_DBUS);
if (g_dbus_message_get_destination (message) == NULL)
g_dbus_message_set_destination (message, client->id);
}
return message;
}
static gboolean
on_new_connection (GDBusServer *server,
GDBusConnection *connection,
gpointer user_data)
{
GDBusDaemon *daemon = user_data;
g_dbus_connection_set_exit_on_close (connection, FALSE);
if (daemon->timeout)
{
g_source_remove (daemon->timeout);
daemon->timeout = 0;
}
client_new (daemon, connection);
return TRUE;
}
static void
g_dbus_daemon_finalize (GObject *object)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
GList *clients, *l;
if (daemon->timeout)
g_source_remove (daemon->timeout);
clients = g_hash_table_get_values (daemon->clients);
for (l = clients; l != NULL; l = l->next)
client_free (l->data);
g_list_free (clients);
g_assert (g_hash_table_size (daemon->clients) == 0);
g_assert (g_hash_table_size (daemon->names) == 0);
g_hash_table_destroy (daemon->clients);
g_hash_table_destroy (daemon->names);
g_object_unref (daemon->server);
if (daemon->tmpdir)
{
g_rmdir (daemon->tmpdir);
g_free (daemon->tmpdir);
}
g_free (daemon->guid);
g_free (daemon->address);
G_OBJECT_CLASS (g_dbus_daemon_parent_class)->finalize (object);
}
static void
g_dbus_daemon_init (GDBusDaemon *daemon)
{
daemon->next_major_id = 1;
daemon->clients = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
daemon->names = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
daemon->guid = g_dbus_generate_guid ();
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (initable);
GDBusServerFlags flags;
flags = G_DBUS_SERVER_FLAGS_NONE;
if (daemon->address == NULL)
{
#ifdef G_OS_UNIX
daemon->tmpdir = g_dir_make_tmp ("gdbus-daemon-XXXXXX", NULL);
daemon->address = g_strdup_printf ("unix:tmpdir=%s", daemon->tmpdir);
flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER;
#else
/* Dont require authentication on Windows as that hasnt been
* implemented yet. */
daemon->address = g_strdup ("nonce-tcp:");
flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
#endif
}
daemon->server = g_dbus_server_new_sync (daemon->address,
flags,
daemon->guid,
NULL,
cancellable,
error);
if (daemon->server == NULL)
return FALSE;
g_dbus_server_start (daemon->server);
g_signal_connect (daemon->server, "new-connection",
G_CALLBACK (on_new_connection),
daemon);
return TRUE;
}
static void
g_dbus_daemon_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
switch (prop_id)
{
case PROP_ADDRESS:
g_free (daemon->address);
daemon->address = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_dbus_daemon_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GDBusDaemon *daemon = G_DBUS_DAEMON (object);
switch (prop_id)
{
case PROP_ADDRESS:
g_value_set_string (value, daemon->address);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_dbus_daemon_class_init (GDBusDaemonClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = g_dbus_daemon_finalize;
gobject_class->set_property = g_dbus_daemon_set_property;
gobject_class->get_property = g_dbus_daemon_get_property;
g_dbus_daemon_signals[SIGNAL_IDLE_TIMEOUT] =
g_signal_new (I_("idle-timeout"),
G_TYPE_DBUS_DAEMON,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
g_object_class_install_property (gobject_class,
PROP_ADDRESS,
g_param_spec_string ("address", NULL, NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static void
g_dbus_daemon_iface_init (_GFreedesktopDBusIface *iface)
{
iface->handle_add_match = handle_add_match;
iface->handle_get_connection_selinux_security_context = handle_get_connection_selinux_security_context;
iface->handle_get_connection_unix_process_id = handle_get_connection_unix_process_id;
iface->handle_get_connection_unix_user = handle_get_connection_unix_user;
iface->handle_get_id = handle_get_id;
iface->handle_get_name_owner = handle_get_name_owner;
iface->handle_hello = handle_hello;
iface->handle_list_activatable_names = handle_list_activatable_names;
iface->handle_list_names = handle_list_names;
iface->handle_list_queued_owners = handle_list_queued_owners;
iface->handle_name_has_owner = handle_name_has_owner;
iface->handle_release_name = handle_release_name;
iface->handle_reload_config = handle_reload_config;
iface->handle_update_activation_environment = handle_update_activation_environment;
iface->handle_remove_match = handle_remove_match;
iface->handle_request_name = handle_request_name;
iface->handle_start_service_by_name = handle_start_service_by_name;
}
static void
initable_iface_init (GInitableIface *initable_iface)
{
initable_iface->init = initable_init;
}
GDBusDaemon *
_g_dbus_daemon_new (const char *address,
GCancellable *cancellable,
GError **error)
{
return g_initable_new (G_TYPE_DBUS_DAEMON,
cancellable,
error,
"address", address,
NULL);
}
const char *
_g_dbus_daemon_get_address (GDBusDaemon *daemon)
{
return g_dbus_server_get_client_address (daemon->server);
}