glib/gio/gdbusdaemon.c
2022-12-16 18:45:37 +01:00

1749 lines
43 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 "glibintl.h"
#include "gdbus-daemon-generated.h"
#define DBUS_SERVICE_NAME "org.freedesktop.DBus"
/* Owner flags */
#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */
#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 /**< Request to replace the current primary owner */
#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x4 /**< If we can not become the primary owner do not place us in the queue */
/* Replies to request for a name */
#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 /**< Service has become the primary owner of the requested name */
#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 /**< Service could not become the primary owner and has been placed in the queue */
#define DBUS_REQUEST_NAME_REPLY_EXISTS 3 /**< Service is already in the queue */
#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 /**< Service is already the primary owner */
/* Replies to releasing a name */
#define DBUS_RELEASE_NAME_REPLY_RELEASED 1 /**< Service was released from the given name */
#define DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 /**< The given name does not exist on the bus */
#define DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 /**< Service is not an owner of the given name */
/* Replies to service starts */
#define DBUS_START_REPLY_SUCCESS 1 /**< Service was auto started */
#define DBUS_START_REPLY_ALREADY_RUNNING 2 /**< Service was already running */
#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)
{
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);
}
}
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, 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_NAME;
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 ("/org/freedesktop/DBus",
"org.freedesktop.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, "/org/freedesktop/DBus",
"org.freedesktop.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, "/org/freedesktop/DBus",
"org.freedesktop.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,
"/org/freedesktop/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_NAME) == 0)
{
_g_freedesktop_dbus_complete_get_name_owner (object, invocation, DBUS_SERVICE_NAME);
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, "/org/freedesktop/DBus",
"org.freedesktop.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_NAME) == 0)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot release a service named " DBUS_SERVICE_NAME ", 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_NAME) == 0)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Cannot acquire a service named " DBUS_SERVICE_NAME ", 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_NAME) != 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_NAME) != 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_NAME);
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",
"Bus Address",
"The address the bus should use",
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);
}