/* * 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 . * * Authors: Alexander Larsson */ #include "config.h" #include #include #include #include #include #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) { /* scan-build with clang-17 can’t 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 don’t * 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_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 /* Don’t require authentication on Windows as that hasn’t 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); }