| /* | |
| * 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 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. */ | |
| #if !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 | |
| /* 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); | |
| } |