/* * This file is part of Cockpit. * * Copyright (C) 2013 Red Hat, Inc. * * Cockpit 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. * * Cockpit 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 Cockpit; If not, see . */ #include "config.h" #include "cockpitdbusjson.h" #include "cockpitchannel.h" #include "cockpitpipechannel.h" #include "cockpitdbuscache.h" #include "cockpitdbusinternal.h" #include "cockpitdbusmeta.h" #include "cockpitdbusrules.h" #include "common/cockpitjson.h" #include #include #include /** * CockpitDBusJson: * * A #CockpitChannel that sends DBus messages with the dbus-json payload * type. */ gboolean cockpit_dbus_json_allow_external = TRUE; /* The maximum number of DBus passed fds open without a channel */ #define MAX_RECEIVED_DBUS_FDS 16 #define COCKPIT_DBUS_JSON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), COCKPIT_TYPE_DBUS_JSON, CockpitDBusJson)) typedef struct { CockpitChannel parent; GDBusConnection *connection; GBusType bus_type; /* Talking to */ const gchar *logname; const gchar *default_name; guint default_watch; gboolean default_watched; gboolean default_appeared; /* Call related */ GCancellable *cancellable; GList *active_calls; GHashTable *interface_info; /* Per name information */ GHashTable *peers; /* Invocations and publishing */ GHashTable *invocations; GHashTable *registered; guint last_invocation; /* File descriptors */ GQueue *fd_channel_ids; } CockpitDBusJson; typedef struct { gchar *name; CockpitDBusJson *dbus_json; guint subscribe_id; gboolean subscribed; /* Signal related */ CockpitDBusRules *rules; /* Watch and introspection */ CockpitDBusCache *cache; gulong meta_sig; gulong update_sig; } CockpitDBusPeer; typedef struct { CockpitChannelClass parent_class; } CockpitDBusJsonClass; G_DEFINE_TYPE (CockpitDBusJson, cockpit_dbus_json, COCKPIT_TYPE_CHANNEL); #if !GLIB_CHECK_VERSION(2,46,0) #define G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION (1<<2) #endif /* * Shared D-Bus connections. * * @group_connections maps "group:address" strings to GDBusConnection * objects. * * @pending_group_connections maps "group:address" strings to GPtrArrays * with CockpitDBusJson objects that are waiting for this connection to * be established. */ static GHashTable *group_connections; static GHashTable *pending_group_connections; static const gchar * value_type_name (JsonNode *node) { GType type = json_node_get_value_type (node); if (type == G_TYPE_STRING) return "string"; else if (type == G_TYPE_INT64) return "int"; else if (type == G_TYPE_DOUBLE) return "double"; else return g_type_name (type); } static gboolean check_type (JsonNode *node, JsonNodeType type, GType sub_type, GError **error) { if (JSON_NODE_TYPE (node) != type || (type == JSON_NODE_VALUE && (json_node_get_value_type (node) != sub_type))) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Unexpected type '%s' in argument", value_type_name (node)); return FALSE; } return TRUE; } static gboolean check_int_type (JsonNode *node, const GVariantType *type, gint64 min, gint64 max, GError **error) { gint64 value; if (!check_type(node, JSON_NODE_VALUE, G_TYPE_INT64, error)) return FALSE; value = json_node_get_int (node); if (value < min || value > max) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Number '%" G_GINT64_FORMAT "' is not in range for the expected type '%.*s'", value, (int) g_variant_type_get_string_length (type), g_variant_type_peek_string (type)); return FALSE; } return TRUE; } static GVariant * parse_json (JsonNode *node, const GVariantType *type, GError **error); static GVariant * parse_json_tuple (JsonNode *node, const GVariantType *child_type, GError **error) { GVariant *result = NULL; GPtrArray *children; GVariant *value; JsonArray *array; guint length; guint i; children = g_ptr_array_new (); if (!check_type (node, JSON_NODE_ARRAY, 0, error)) goto out; array = json_node_get_array (node); length = json_array_get_length (array); for (i = 0; i < length; i++) { value = NULL; if (child_type == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Too many values in tuple/struct"); } else { value = parse_json (json_array_get_element (array, i), child_type, error); } if (!value) goto out; g_ptr_array_add (children, value); child_type = g_variant_type_next (child_type); } if (child_type) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Too few values in tuple/struct"); goto out; } result = g_variant_new_tuple ((GVariant *const *)children->pdata, children->len); children->len = 0; out: g_ptr_array_foreach (children, (GFunc)g_variant_unref, NULL); g_ptr_array_free (children, TRUE); return result; } static GVariant * parse_json_byte_array (JsonNode *node, GError **error) { static const char valid[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; GVariant *result = NULL; const gchar *value; gpointer data = NULL; gsize length; gsize pos; if (!check_type (node, JSON_NODE_VALUE, G_TYPE_STRING, error)) goto out; value = json_node_get_string (node); pos = strspn (value, valid); while (value[pos] == '=') pos++; if (pos == 0) { data = NULL; length = 0; } else { /* base64 strings are always multiple of 3 */ if (pos % 3 == 0 || value[pos] == '\0') data = g_base64_decode (value, &length); if (!data) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid base64 in argument"); goto out; } } result = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING, data, length, TRUE, g_free, data); out: return result; } static GVariant * parse_json_array (JsonNode *node, const GVariantType *child_type, GError **error) { GVariant *result = NULL; GPtrArray *children; GVariant *child; JsonArray *array; guint length; guint i; children = g_ptr_array_new (); if (!check_type (node, JSON_NODE_ARRAY, 0, error)) goto out; array = json_node_get_array (node); length = json_array_get_length (array); for (i = 0; i < length; i++) { child = parse_json (json_array_get_element (array, i), child_type, error); if (!child) goto out; g_ptr_array_add (children, child); } result = g_variant_new_array (child_type, (GVariant *const *)children->pdata, children->len); children->len = 0; out: g_ptr_array_foreach (children, (GFunc)g_variant_unref, NULL); g_ptr_array_free (children, TRUE); return result; } static GVariant * parse_json_variant (JsonNode *node, GError **error) { GVariantType *inner_type; JsonObject *object; GVariant *inner; JsonNode *val; const gchar *sig; if (!check_type (node, JSON_NODE_OBJECT, 0, error)) return NULL; object = json_node_get_object (node); val = json_object_get_member (object, "v"); if (val == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Variant object did not contain a 'v' field"); return NULL; } if (!cockpit_json_get_string (object, "t", NULL, &sig) || !sig) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Variant object did not contain valid 't' field"); return NULL; } if (!g_variant_type_string_is_valid (sig)) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Variant 't' field '%s' is invalid", sig); return NULL; } inner_type = g_variant_type_new (sig); inner = parse_json (val, inner_type, error); g_variant_type_free (inner_type); if (!inner) return NULL; return g_variant_new_variant (inner); } static GVariant * parse_json_dictionary (JsonNode *node, const GVariantType *entry_type, GError **error) { const GVariantType *key_type; const GVariantType *value_type; GVariant *result = NULL; GPtrArray *children; JsonObject *object; JsonNode *key_node; GList *members = NULL; gboolean is_string; GVariant *value; GVariant *key; GVariant *child; GList *l; children = g_ptr_array_new (); if (!check_type (node, JSON_NODE_OBJECT, 0, error)) goto out; object = json_node_get_object (node); key_type = g_variant_type_key (entry_type); value_type = g_variant_type_value (entry_type); is_string = (g_variant_type_equal (key_type, G_VARIANT_TYPE_STRING) || g_variant_type_equal (key_type, G_VARIANT_TYPE_OBJECT_PATH) || g_variant_type_equal (key_type, G_VARIANT_TYPE_SIGNATURE)); members = json_object_get_members (object); for (l = members; l != NULL; l = g_list_next (l)) { if (is_string) { key_node = json_node_new (JSON_NODE_VALUE); json_node_set_string (key_node, l->data); } else { key_node = cockpit_json_parse (l->data, -1, NULL); if (key_node == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Unexpected key '%s' in dict entry", (gchar *)l->data); goto out; } } key = parse_json (key_node, key_type, error); json_node_free (key_node); if (!key) goto out; value = parse_json (json_object_get_member (object, l->data), value_type, error); if (!value) { g_variant_unref (key); goto out; } child = g_variant_new_dict_entry (key, value); g_ptr_array_add (children, child); } result = g_variant_new_array (entry_type, (GVariant *const *)children->pdata, children->len); children->len = 0; out: g_list_free (members); g_ptr_array_foreach (children, (GFunc)g_variant_unref, NULL); g_ptr_array_free (children, TRUE); return result; } static GVariant * parse_json_object_path (JsonNode *node, GError **error) { GVariant *result = NULL; const gchar *str; if (!check_type (node, JSON_NODE_VALUE, G_TYPE_STRING, error)) return NULL; str = json_node_get_string (node); if (g_variant_is_object_path (str)) { result = g_variant_new_object_path (str); } else { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid object path '%s'", str); } return result; } static GVariant * parse_json_signature (JsonNode *node, GError **error) { const gchar *str; if (!check_type (node, JSON_NODE_VALUE, G_TYPE_STRING, error)) return NULL; str = json_node_get_string (node); if (g_variant_is_signature (str)) return g_variant_new_signature (str); else { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid signature '%s'", str); return NULL; } } static void parse_not_supported (const GVariantType *type, GError **error) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Type '%.*s' is unknown or not supported", (int)g_variant_type_get_string_length (type), g_variant_type_peek_string (type)); } static GVariant * parse_json (JsonNode *node, const GVariantType *type, GError **error) { const GVariantType *element_type; if (!g_variant_type_is_definite (type)) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Indefinite type '%.*s' is not supported", (int)g_variant_type_get_string_length (type), g_variant_type_peek_string (type)); return NULL; } if (g_variant_type_is_basic (type)) { if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) { if (check_type (node, JSON_NODE_VALUE, G_TYPE_BOOLEAN, error)) return g_variant_new_boolean (json_node_get_boolean (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) { if (check_int_type (node, type, 0, G_MAXUINT8, error)) return g_variant_new_byte (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16)) { if (check_int_type (node, type, G_MININT16, G_MAXINT16, error)) return g_variant_new_int16 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16)) { if (check_int_type (node, type, 0, G_MAXUINT16, error)) return g_variant_new_uint16 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32)) { if (check_int_type (node, type, G_MININT32, G_MAXINT32, error)) return g_variant_new_int32 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32)) { if (check_int_type (node, type, 0, G_MAXUINT32, error)) return g_variant_new_uint32 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64)) { if (check_int_type (node, type, G_MININT64, G_MAXINT64, error)) return g_variant_new_int64 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64)) { /* Can never be larger than signed int64, because json-glib * only returns doubles or signed 64-bit integers when * encountering a JSON number. */ if (check_int_type (node, type, 0, G_MAXINT64, error)) return g_variant_new_uint64 (json_node_get_int (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE)) { if (check_type (node, JSON_NODE_VALUE, G_TYPE_INT64, NULL)) return g_variant_new_double (json_node_get_int (node)); else if (check_type (node, JSON_NODE_VALUE, G_TYPE_DOUBLE, error)) return g_variant_new_double (json_node_get_double (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) { if (check_type (node, JSON_NODE_VALUE, G_TYPE_STRING, error)) return g_variant_new_string (json_node_get_string (node)); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH)) { return parse_json_object_path (node, error); } else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE)) { return parse_json_signature (node, error); } else { parse_not_supported (type, error); } } else if (g_variant_type_is_variant (type)) { return parse_json_variant (node, error); } else if (g_variant_type_is_array (type)) { element_type = g_variant_type_element (type); if (g_variant_type_equal (element_type, G_VARIANT_TYPE_BYTE)) return parse_json_byte_array (node, error); else if (g_variant_type_is_dict_entry (element_type)) return parse_json_dictionary (node, element_type, error); else return parse_json_array (node, element_type, error); } else if (g_variant_type_is_tuple (type)) { return parse_json_tuple (node, g_variant_type_first (type), error); } else { parse_not_supported (type, error); } return NULL; } typedef struct { GUnixFDList *fdlist; GQueue *fdids; } VariantContext; static JsonNode * build_json (GVariant *value, VariantContext *context); static JsonObject * build_json_variant (GVariant *value, VariantContext *context) { GVariant *child; JsonObject *object; child = g_variant_get_variant (value); object = json_object_new (); json_object_set_string_member (object, "t", g_variant_get_type_string (child)); json_object_set_member (object, "v", build_json (child, context)); g_variant_unref (child); return object; } static JsonNode * build_json_byte_array (GVariant *value) { JsonNode *node; gconstpointer data; gsize length = 0; gchar *string; data = g_variant_get_fixed_array (value, &length, 1); if (length > 0) string = g_base64_encode (data, length); else string = NULL; node = json_node_new (JSON_NODE_VALUE); json_node_set_string (node, string ? string : ""); g_free (string); /* unfortunately :S */ return node; } static JsonArray * build_json_array_or_tuple (GVariant *value, VariantContext *context) { GVariantIter iter; GVariant *child; JsonArray *array; array = json_array_new (); g_variant_iter_init (&iter, value); while ((child = g_variant_iter_next_value (&iter)) != NULL) { json_array_add_element (array, build_json (child, context)); g_variant_unref (child); } return array; } static JsonObject * build_json_dictionary (const GVariantType *entry_type, GVariant *dict, VariantContext *context) { const GVariantType *key_type; GVariantIter iter; GVariant *child; GVariant *key; GVariant *value; gboolean is_string; gchar *key_string; JsonObject *object; object = json_object_new (); key_type = g_variant_type_key (entry_type); is_string = (g_variant_type_equal (key_type, G_VARIANT_TYPE_STRING) || g_variant_type_equal (key_type, G_VARIANT_TYPE_OBJECT_PATH) || g_variant_type_equal (key_type, G_VARIANT_TYPE_SIGNATURE)); g_variant_iter_init (&iter, dict); while ((child = g_variant_iter_next_value (&iter)) != NULL) { key = g_variant_get_child_value (child, 0); value = g_variant_get_child_value (child, 1); if (is_string) { json_object_set_member (object, g_variant_get_string (key, NULL), build_json (value, context)); } else { key_string = g_variant_print (key, FALSE); json_object_set_member (object, key_string, build_json (value, context)); g_free (key_string); } g_variant_unref (key); g_variant_unref (value); } return object; } static JsonNode * build_json_fd_channel (GVariant *value, VariantContext *context) { JsonObject *object; GError *error = NULL; JsonNode *node; gint fd = -1; gchar *old; const gchar *id; if (context->fdlist) { fd = g_unix_fd_list_get (context->fdlist, g_variant_get_handle (value), &error); if (fd == -1) { g_warning ("couldn't dup file descriptor from DBus message: %s", error->message); g_clear_error (&error); } } if (fd < 0) { node = json_node_new (JSON_NODE_NULL); } else { g_assert (context->fdlist != NULL); g_assert (context->fdids != NULL); node = json_node_new (JSON_NODE_OBJECT); /* Add a new internal channel name for this file descriptor */ id = cockpit_pipe_channel_add_internal_fd (fd); g_queue_push_tail (context->fdids, (gpointer) g_strdup (id)); /* And only keep the last N ready for opening channels */ while (g_queue_get_length (context->fdids) > MAX_RECEIVED_DBUS_FDS) { old = (gchar *)g_queue_pop_head (context->fdids); cockpit_pipe_channel_remove_internal_fd (old); g_free (old); } /* This is sent back as the list of channel options to use */ object = json_object_new (); json_object_set_string_member (object, "payload", "stream"); json_object_set_string_member (object, "internal", id); json_node_set_object (node, object); json_object_unref (object); } return node; } static JsonNode * build_json (GVariant *value, VariantContext *context) { const GVariantType *type; const GVariantType *element_type; JsonObject *object; JsonArray *array; JsonNode *node; switch (g_variant_classify (value)) { case G_VARIANT_CLASS_BOOLEAN: node = json_node_new (JSON_NODE_VALUE); json_node_set_boolean (node, g_variant_get_boolean (value)); return node; case G_VARIANT_CLASS_BYTE: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_byte (value)); return node; case G_VARIANT_CLASS_INT16: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_int16 (value)); return node; case G_VARIANT_CLASS_UINT16: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_uint16 (value)); return node; case G_VARIANT_CLASS_INT32: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_int32 (value)); return node; case G_VARIANT_CLASS_UINT32: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_uint32 (value)); return node; case G_VARIANT_CLASS_INT64: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_int64 (value)); return node; case G_VARIANT_CLASS_UINT64: node = json_node_new (JSON_NODE_VALUE); json_node_set_int (node, g_variant_get_uint64 (value)); return node; case G_VARIANT_CLASS_HANDLE: node = build_json_fd_channel (value, context); return node; case G_VARIANT_CLASS_DOUBLE: node = json_node_new (JSON_NODE_VALUE); json_node_set_double (node, g_variant_get_double (value)); return node; case G_VARIANT_CLASS_STRING: /* explicit fall-through */ case G_VARIANT_CLASS_OBJECT_PATH: /* explicit fall-through */ case G_VARIANT_CLASS_SIGNATURE: node = json_node_new (JSON_NODE_VALUE); json_node_set_string (node, g_variant_get_string (value, NULL)); return node; case G_VARIANT_CLASS_VARIANT: object = build_json_variant (value, context); node = json_node_new (JSON_NODE_OBJECT); json_node_take_object (node, object); return node; case G_VARIANT_CLASS_ARRAY: type = g_variant_get_type (value); element_type = g_variant_type_element (type); if (g_variant_type_is_dict_entry (element_type)) { object = build_json_dictionary (element_type, value, context); node = json_node_new (JSON_NODE_OBJECT); json_node_take_object (node, object); } else if (g_variant_type_equal (element_type, G_VARIANT_TYPE_BYTE)) { node = build_json_byte_array (value); } else { array = build_json_array_or_tuple (value, context); node = json_node_new (JSON_NODE_ARRAY); json_node_set_array (node, array); json_array_unref (array); } return node; case G_VARIANT_CLASS_TUPLE: array = build_json_array_or_tuple (value, context); node = json_node_new (JSON_NODE_ARRAY); json_node_set_array (node, array); json_array_unref (array); return node; case G_VARIANT_CLASS_DICT_ENTRY: case G_VARIANT_CLASS_MAYBE: default: g_return_val_if_reached (NULL); break; } } static void send_json_object (CockpitDBusJson *self, JsonObject *object) { GBytes *bytes; bytes = cockpit_json_write_bytes (object); cockpit_channel_send (COCKPIT_CHANNEL (self), bytes, TRUE); g_bytes_unref (bytes); } static JsonObject * build_json_error (GError *error) { JsonObject *object; JsonArray *reply; JsonArray *args; gchar *error_name; object = json_object_new (); reply = json_array_new (); args = json_array_new (); error_name = g_dbus_error_get_remote_error (error); g_dbus_error_strip_remote_error (error); json_array_add_string_element (reply, error_name != NULL ? error_name : ""); g_free (error_name); if (error->message) json_array_add_string_element (args, error->message); json_array_add_array_element (reply, args); json_object_set_array_member (object, "error", reply); return object; } static gchar * build_signature (GVariant *variant) { const GVariantType *type; GString *sig; sig = g_string_new (""); type = g_variant_get_type (variant); for (type = g_variant_type_first (type); type != NULL; type = g_variant_type_next (type)) { g_string_append_len (sig, g_variant_type_peek_string (type), g_variant_type_get_string_length (type)); } return g_string_free (sig, FALSE); } static JsonNode * build_json_body (GVariant *body, VariantContext *context, gchar **type) { if (body) { if (type) *type = build_signature (body); return build_json (body, context); } else { if (type) *type = NULL; return json_node_new (JSON_NODE_NULL); } } static JsonObject * build_json_signal (const gchar *path, const gchar *interface, const gchar *member, GVariant *body) { JsonObject *object; JsonArray *signal; VariantContext context = { NULL }; object = json_object_new (); signal = json_array_new (); json_array_add_string_element (signal, path); json_array_add_string_element (signal, interface); json_array_add_string_element (signal, member); json_array_add_element (signal, build_json_body (body, &context, NULL)); json_object_set_array_member (object, "signal", signal); return object; } /* ---------------------------------------------------------------------------------------------------- */ typedef struct { /* Cleared by dispose */ GList *link; CockpitDBusJson *dbus_json; /* Request data */ JsonObject *request; /* Owned here */ GVariantType *param_type; /* Owned by request */ const gchar *cookie; const gchar *name; const gchar *interface; const gchar *method; const gchar *path; const gchar *type; const gchar *flags; gint timeout; JsonNode *args; } CallData; static CockpitDBusPeer * ensure_peer (CockpitDBusJson *self, const gchar *name); static void send_dbus_error (CockpitDBusJson *self, CallData *call, GError *error) { JsonObject *object; if (!call->cookie) { g_debug ("%s: dropping error without cookie: %s", self->logname, error->message); return; } g_debug ("%s: failed %s", self->logname, call->method); object = build_json_error (error); json_object_set_string_member (object, "id", call->cookie); send_json_object (self, object); json_object_unref (object); } typedef struct { CockpitDBusJson *dbus_json; JsonObject *message; } WaitData; static void on_wait_complete (CockpitDBusCache *cache, gpointer user_data) { WaitData *wd = user_data; CockpitDBusJson *self = wd->dbus_json; if (!g_cancellable_is_cancelled (self->cancellable)) send_json_object (self, wd->message); g_object_unref (wd->dbus_json); json_object_unref (wd->message); g_slice_free (WaitData, wd); } static void send_with_barrier (CockpitDBusJson *self, CockpitDBusPeer *peer, JsonObject *message) { WaitData *wd = g_slice_new (WaitData); wd->dbus_json = g_object_ref (self); wd->message = json_object_ref (message); cockpit_dbus_cache_barrier (peer->cache, on_wait_complete, wd); } static void send_dbus_reply (CockpitDBusJson *self, CallData *call, GDBusMessage *message) { CockpitDBusPeer *peer; VariantContext context = { NULL }; GVariant *scrape = NULL; JsonObject *object; GString *flags; g_return_if_fail (call->cookie != NULL); JsonArray *reply; gchar *type = NULL; object = json_object_new (); reply = json_array_new (); if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_ERROR) { g_debug ("%s: errorc for %s", self->logname, call->method); json_array_add_string_element (reply, g_dbus_message_get_error_name (message)); json_object_set_array_member (object, "error", reply); } else { g_debug ("%s: reply for %s", self->logname, call->method); json_object_set_array_member (object, "reply", reply); scrape = g_dbus_message_get_body (message); } context.fdlist = g_dbus_message_get_unix_fd_list (message); if (context.fdlist) { if (!self->fd_channel_ids) self->fd_channel_ids = g_queue_new (); context.fdids = self->fd_channel_ids; } json_array_add_element (reply, build_json_body (g_dbus_message_get_body (message), &context, call->type != NULL ? &type : NULL)); if (type) { json_object_set_string_member (object, "type", type); g_free (type); } json_object_set_string_member (object, "id", call->cookie); if (call->flags) { flags = g_string_new (""); if (g_dbus_message_get_byte_order (message) == G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN) g_string_append_c (flags, '>'); else g_string_append_c (flags, '<'); json_object_set_string_member (object, "flags", flags->str); g_string_free (flags, TRUE); } peer = ensure_peer (self, call->name); cockpit_dbus_cache_poke (peer->cache, call->path, call->interface); if (scrape) cockpit_dbus_cache_scrape (peer->cache, scrape); send_with_barrier (self, peer, object); json_object_unref (object); } static GVariantType * calculate_method_param_type (GDBusInterfaceInfo *info, const gchar *iface, const gchar *method, GError **error) { const GVariantType *arg_types[256]; GDBusMethodInfo *method_info = NULL; guint n; if (info) method_info = g_dbus_interface_info_lookup_method (info, method); if (method_info == NULL) { g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Introspection data for method %s %s not available", iface, method); return NULL; } else if (method_info->in_args) { for (n = 0; method_info->in_args[n] != NULL; n++) { /* DBus places a hard limit of 255 on signature length. * therefore number of args must be less than 256. */ if (n >= G_N_ELEMENTS (arg_types)) return NULL; arg_types[n] = G_VARIANT_TYPE (method_info->in_args[n]->signature); if G_UNLIKELY (arg_types[n] == NULL) return NULL; } } else { n = 0; } return g_variant_type_new_tuple (arg_types, n); } static void call_data_free (CallData *call) { if (call->dbus_json) call->dbus_json->active_calls = g_list_delete_link (call->dbus_json->active_calls, call->link); if (call->request) json_object_unref (call->request); if (call->param_type) g_variant_type_free (call->param_type); g_slice_free (CallData, call); } static void on_send_message_reply (GObject *source, GAsyncResult *result, gpointer user_data) { CallData *call = user_data; GError *error = NULL; GDBusMessage *message; message = g_dbus_connection_send_message_with_reply_finish (G_DBUS_CONNECTION (source), result, &error); if (call->dbus_json) { if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_clear_error (&error); g_set_error (&error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT, "method call %s timed out", call->method); } send_dbus_error (call->dbus_json, call, error); } else { send_dbus_reply (call->dbus_json, call, message); } } g_clear_error (&error); g_clear_object (&message); call_data_free (call); } static void handle_dbus_call_on_interface (CockpitDBusJson *self, CallData *call) { GVariant *parameters = NULL; GError *error = NULL; GDBusMessage *message = NULL; g_return_if_fail (call->param_type != NULL); parameters = parse_json (call->args, call->param_type, &error); if (!parameters) goto out; g_debug ("%s: invoking %s %s at %s", self->logname, call->interface, call->method, call->path); message = g_dbus_message_new_method_call (call->name, call->path, call->interface, call->method); /* When no flags or interactive flags not set */ if (!call->flags || strchr (call->flags, 'i')) g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION); g_dbus_message_set_body (message, parameters); parameters = NULL; g_dbus_connection_send_message_with_reply (self->connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, call->timeout, NULL, /* serial */ self->cancellable, call->cookie ? on_send_message_reply : NULL, call->cookie ? call : NULL); if (call->cookie) call = NULL; /* ownership assumed above */ out: if (error) { if (call) send_dbus_error (self, call, error); g_error_free (error); } if (call) call_data_free (call); if (message) g_object_unref (message); } static void on_introspect_ready (CockpitDBusCache *cache, GDBusInterfaceInfo *iface, gpointer user_data) { CallData *call = user_data; CockpitDBusJson *self = call->dbus_json; GError *error = NULL; /* Cancelled? */ if (!call->dbus_json) { call_data_free (call); return; } call->param_type = calculate_method_param_type (iface, call->interface, call->method, &error); if (error) { send_dbus_error (self, call, error); g_error_free (error); call_data_free (call); } else { handle_dbus_call_on_interface (self, call); } } static const gchar * array_string_element (JsonArray *array, guint i) { JsonNode *node; node = json_array_get_element (array, i); if (node && JSON_NODE_HOLDS_VALUE (node) && json_node_get_value_type (node) == G_TYPE_STRING) return json_node_get_string (node); return NULL; } static gboolean parse_json_method (CockpitDBusJson *self, JsonNode *node, const gchar *description, const gchar **path, const gchar **interface, const gchar **method, JsonNode **args) { JsonArray *array; g_assert (description != NULL); g_assert (path != NULL); g_assert (interface != NULL); g_assert (method != NULL); g_assert (args != NULL); if (!JSON_NODE_HOLDS_ARRAY (node)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "incorrect '%s' field in dbus command", description); return FALSE; } array = json_node_get_array (node); *path = array_string_element (array, 0); *interface = array_string_element (array, 1); *method = array_string_element (array, 2); *args = json_array_get_element (array, 3); if (!*args || !JSON_NODE_HOLDS_ARRAY (*args)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "arguments field is invalid in dbus \"%s\"", description); } else if (!*path || !g_variant_is_object_path (*path)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "object path is invalid in dbus \"%s\": %s", description, *path); } else if (!*interface || !g_dbus_is_interface_name (*interface)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "interface name is invalid in dbus \"%s\": %s", description, *interface); } else if (!*method || !g_dbus_is_member_name (*method)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "member name is invalid in dbus \"%s\": %s", description, *method); } else { return TRUE; } return FALSE; } static void handle_dbus_call (CockpitDBusJson *self, JsonObject *object) { CockpitDBusPeer *peer = NULL; CallData *call; JsonNode *node; gchar *string; gint64 timeout; node = json_object_get_member (object, "call"); g_return_if_fail (node != NULL); call = g_slice_new0 (CallData); if (!parse_json_method (self, node, "call", &call->path, &call->interface, &call->method, &call->args)) { /* fall through to call invalid */ } else if (!cockpit_json_get_string (object, "name", self->default_name, &call->name)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"name\" field is invalid in dbus call"); } else if (self->bus_type != G_BUS_TYPE_NONE && call->name == NULL) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"name\" field is missing in dbus call"); } else if (call->name != NULL && !g_dbus_is_name (call->name)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"name\" field in dbus call is not a valid bus name: %s", call->name); } else if (!cockpit_json_get_string (object, "id", NULL, &call->cookie)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"id\" field is invalid in call"); } else if (!cockpit_json_get_string (object, "type", NULL, &call->type)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"type\" field is invalid in call"); } else if (call->type && !g_variant_is_signature (call->type)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"type\" signature is not valid in dbus call: %s", call->type); } else if (!cockpit_json_get_string (object, "flags", NULL, &call->flags)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"flags\" field is invalid in dbus call"); } else if (!cockpit_json_get_int(object, "timeout", G_MAXINT, &timeout) || timeout <= 0 || timeout > G_MAXINT) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the \"timeout\" field is invalid in dbus call"); } else { if (call->type) { string = g_strdup_printf ("(%s)", call->type); call->param_type = g_variant_type_new (string); g_free (string); } /* No arguments or zero arguments, can make call without introspecting */ if (!call->param_type) { if (json_array_get_length (json_node_get_array (call->args)) == 0) call->param_type = g_variant_type_new ("()"); } call->dbus_json = self; call->request = json_object_ref (object); self->active_calls = g_list_prepend (self->active_calls, call); call->link = g_list_find (self->active_calls, call); call->timeout = timeout; if (call->param_type) { /* Frees call data when done */ handle_dbus_call_on_interface (self, call); } else { peer = ensure_peer (self, call->name); cockpit_dbus_cache_introspect (peer->cache, call->path, call->interface, on_introspect_ready, call); } /* Start processing call */ return; } /* call was invalid */ call_data_free (call); } static GVariantType * calculate_signal_param_type (CockpitDBusJson *self, const gchar *iface, const gchar *signal) { GDBusInterfaceInfo *info; const GVariantType *arg_types[256]; GDBusSignalInfo *signal_info = NULL; guint n; info = g_hash_table_lookup (self->interface_info, iface); if (info) signal_info = g_dbus_interface_info_lookup_signal (info, signal); if (signal_info == NULL) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "signal argument types for signal %s %s unknown", iface, signal); return NULL; } else if (signal_info->args) { for (n = 0; signal_info->args[n] != NULL; n++) { /* DBus places a hard limit of 255 on signature length. * therefore number of args must be less than 256. */ if (n >= G_N_ELEMENTS (arg_types)) return NULL; arg_types[n] = G_VARIANT_TYPE (signal_info->args[n]->signature); if G_UNLIKELY (arg_types[n] == NULL) return NULL; } } else { n = 0; } return g_variant_type_new_tuple (arg_types, n); } static void handle_dbus_signal_on_interface (CockpitDBusJson *self, GVariantType *param_type, const gchar *destination, const gchar *path, const gchar *interface, const gchar *signal, JsonNode *args) { CockpitChannel *channel; GVariant *parameters = NULL; GDBusMessage *message = NULL; GError *error = NULL; parameters = parse_json (args, param_type, &error); if (parameters) { g_debug ("%s: signal %s %s at %s", self->logname, interface, signal, path); message = g_dbus_message_new_signal (path, interface, signal); g_dbus_message_set_body (message, parameters); parameters = NULL; if (destination) g_dbus_message_set_destination (message, destination); g_dbus_connection_send_message (self->connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, &error); } if (error) { channel = COCKPIT_CHANNEL (self); if (error->code == G_IO_ERROR_INVALID_ARGUMENT || error->code == G_DBUS_ERROR_INVALID_ARGS) { cockpit_channel_fail (channel, "protocol-error", "%s", error->message); } else { cockpit_channel_fail (channel, "internal-error", "%s", error->message); } g_error_free (error); } if (message) g_object_unref (message); } static void handle_dbus_signal (CockpitDBusJson *self, JsonObject *object) { GVariantType *param_type = NULL; const gchar *interface; const gchar *destination; const gchar *path; const gchar *type; const gchar *flags; const gchar *signal; JsonNode *node; JsonNode *args; gchar *string; node = json_object_get_member (object, "signal"); g_return_if_fail (node != NULL); if (!parse_json_method (self, node, "signal", &path, &interface, &signal, &args)) { /* fall through to call invalid */ } else if (!cockpit_json_get_string (object, "name", NULL, &destination)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the 'name' field is invalid in signal"); } else if (destination != NULL && !g_dbus_is_name (destination)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the 'name' field is not a valid bus name: %s", destination); } else if (!cockpit_json_get_string (object, "type", NULL, &type)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the 'type' field is invalid in dbus signal"); } else if (type && !g_variant_is_signature (type)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "type signature is not valid in dbus signal: %s", type); } else if (!cockpit_json_get_string (object, "flags", NULL, &flags)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "protocol-error", "the 'flags' field is invalid in call"); } else { if (type) { string = g_strdup_printf ("(%s)", type); param_type = g_variant_type_new (string); g_free (string); } else { param_type = calculate_signal_param_type (self, interface, signal); } if (param_type) { handle_dbus_signal_on_interface (self, param_type, destination, path, interface, signal, args); } } g_variant_type_free (param_type); } static void on_add_match_ready (GObject *source, GAsyncResult *result, gpointer user_data) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (user_data); GError *error = NULL; GVariant *retval; retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); if (error) { if (!g_cancellable_is_cancelled (self->cancellable) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "internal-error", "couldn't add match to bus: %s", error->message); } g_error_free (error); } if (retval) g_variant_unref (retval); g_object_unref (self); } static gboolean parse_json_rule (CockpitDBusJson *self, JsonNode *node, const gchar **name, const gchar **path, const gchar **path_namespace, const gchar **interface, const gchar **signal, const gchar **arg0) { CockpitChannel *channel = COCKPIT_CHANNEL (self); JsonObject *object; gboolean valid; GList *names, *l; if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (channel, "protocol-error", "incorrect match field in dbus command"); return FALSE; } object = json_node_get_object (node); if (name) *name = NULL; if (path) *path = NULL; if (path_namespace) *path_namespace = NULL; if (signal) *signal = NULL; if (interface) *interface = NULL; if (arg0) *arg0 = NULL; names = json_object_get_members (object); for (l = names; l != NULL; l = g_list_next (l)) { valid = FALSE; if (name && g_str_equal (l->data, "name")) valid = cockpit_json_get_string (object, "name", NULL, name); if (interface && g_str_equal (l->data, "interface")) valid = cockpit_json_get_string (object, "interface", NULL, interface); else if (signal && g_str_equal (l->data, "member")) valid = cockpit_json_get_string (object, "member", NULL, signal); else if (path && g_str_equal (l->data, "path")) valid = cockpit_json_get_string (object, "path", NULL, path); else if (path_namespace && g_str_equal (l->data, "path_namespace")) valid = cockpit_json_get_string (object, "path_namespace", NULL, path_namespace); else if (arg0 && g_str_equal (l->data, "arg0")) valid = cockpit_json_get_string (object, "arg0", NULL, arg0); if (!valid) { cockpit_channel_fail (channel, "protocol-error", "invalid or unsupported match field: %s", (gchar *)l->data); g_list_free (names); return FALSE; } } g_list_free (names); valid = FALSE; if (name && *name && !g_dbus_is_name (*name)) cockpit_channel_fail (channel, "protocol-error", "match \"name\" is not valid: %s", *name); else if (path && *path && !g_variant_is_object_path (*path)) cockpit_channel_fail (channel, "protocol-error", "match path is not valid: %s", *path); else if (path_namespace && *path_namespace && !g_variant_is_object_path (*path_namespace)) cockpit_channel_fail (channel, "protocol-error", "match path_namespace is not valid: %s", *path_namespace); else if (interface && *interface && !g_dbus_is_interface_name (*interface)) cockpit_channel_fail (channel, "protocol-error", "match interface is not valid: %s", *interface); else if (signal && *signal && !g_dbus_is_member_name (*signal)) cockpit_channel_fail (channel, "protocol-error", "match \"member\" is not valid: %s", *signal); else if (arg0 && *arg0 && strchr (*arg0, '\'') != NULL) cockpit_channel_fail (channel, "protocol-error", "match arg0 is not valid: %s", *arg0); else if (path && path_namespace && *path && *path_namespace) cockpit_channel_fail (channel, "protocol-error", "match cannot specify both path and path_namespace"); else valid = TRUE; if (name) { if (!*name) *name = self->default_name; if (!*name && self->bus_type != G_BUS_TYPE_NONE) { cockpit_channel_fail (channel, "protocol-error", "%s: no \"name\" specified in match", self->logname); valid = FALSE; } } return valid; } static gchar * build_dbus_match (CockpitDBusJson *self, const gchar *name, const gchar *path, const gchar *path_namespace, const gchar *interface, const gchar *signal, const gchar *arg0) { GString *string = g_string_new ("type='signal'"); if (!name) name = self->default_name; if (name) g_string_append_printf (string, ",sender='%s'", name); if (path) g_string_append_printf (string, ",path='%s'", path); if (path_namespace && !g_str_equal (path_namespace, "/")) g_string_append_printf (string, ",path_namespace='%s'", path_namespace); if (interface) g_string_append_printf (string, ",interface='%s'", interface); if (signal) g_string_append_printf (string, ",member='%s'", signal); if (arg0) g_string_append_printf (string, ",arg0='%s'", arg0); return g_string_free (string, FALSE); } static void handle_dbus_add_match (CockpitDBusJson *self, JsonObject *object) { CockpitDBusPeer *peer = NULL; JsonNode *node; const gchar *name; const gchar *path; const gchar *path_namespace; const gchar *interface; const gchar *signal; const gchar *arg0; gchar *match; node = json_object_get_member (object, "add-match"); g_return_if_fail (node != NULL); if (!parse_json_rule (self, node, &name, &path, &path_namespace, &interface, &signal, &arg0)) return; peer = ensure_peer (self, name); if (cockpit_dbus_rules_add (peer->rules, path ? path : path_namespace, path_namespace ? TRUE : FALSE, interface, signal, arg0)) { if (peer->name) { match = build_dbus_match (self, name, path, path_namespace, interface, signal, arg0); g_dbus_connection_call (self->connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch", g_variant_new ("(s)", match), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, self->cancellable, on_add_match_ready, g_object_ref (self)); g_free (match); } } } static void on_remove_match_ready (GObject *source, GAsyncResult *result, gpointer user_data) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (user_data); GError *error = NULL; GVariant *retval; retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); if (error) { if (!g_cancellable_is_cancelled (self->cancellable) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)) { cockpit_channel_fail (COCKPIT_CHANNEL (self), "internal-error", "couldn't remove match from bus: %s", error->message); } g_error_free (error); } if (retval) g_variant_unref (retval); g_object_unref (self); } static void handle_dbus_remove_match (CockpitDBusJson *self, JsonObject *object) { CockpitDBusPeer *peer; JsonNode *node; const gchar *name; const gchar *path; const gchar *path_namespace; const gchar *interface; const gchar *signal; const gchar *arg0; gchar *match; node = json_object_get_member (object, "remove-match"); g_return_if_fail (node != NULL); if (!parse_json_rule (self, node, &name, &path, &path_namespace, &interface, &signal, &arg0)) return; peer = ensure_peer (self, name); if (cockpit_dbus_rules_remove (peer->rules, path ? path : path_namespace, path_namespace ? TRUE : FALSE, interface, signal, arg0)) { if (peer->name) { match = build_dbus_match (self, name, path, path_namespace, interface, signal, arg0); g_dbus_connection_call (self->connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch", g_variant_new ("(s)", match), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, /* don't cancel removes */ on_remove_match_ready, g_object_ref (self)); g_free (match); } } } static void maybe_include_name (CockpitDBusJson *self, JsonObject *object, const gchar *name) { if (name && g_strcmp0 (name, self->default_name) != 0) json_object_set_string_member (object, "name", name); } static void on_cache_meta (CockpitDBusCache *cache, GDBusInterfaceInfo *iface, gpointer user_data) { CockpitDBusPeer *peer = user_data; JsonObject *interface; JsonObject *meta; JsonObject *message; interface = cockpit_dbus_meta_build (iface); meta = json_object_new (); json_object_set_object_member (meta, iface->name, interface); message = json_object_new (); json_object_set_object_member (message, "meta", meta); maybe_include_name (peer->dbus_json, message, peer->name); send_json_object (peer->dbus_json, message); json_object_unref (message); } static void handle_dbus_meta (CockpitDBusJson *self, JsonObject *object) { CockpitChannel *channel = COCKPIT_CHANNEL (self); GDBusInterfaceInfo *iface; JsonObject *interface; GError *error = NULL; JsonObject *meta; GList *names, *l; JsonNode *node; node = json_object_get_member (object, "meta"); g_return_if_fail (node != NULL); if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (channel, "protocol-error", "incorrect \"meta\" field in dbus command"); return; } meta = json_node_get_object (node); names = json_object_get_members (meta); for (l = names; l != NULL; l = g_list_next (l)) { if (!cockpit_json_get_object (meta, l->data, NULL, &interface)) { cockpit_channel_fail (channel, "protocol-error", "invalid interface in dbus \"meta\" command"); break; } iface = cockpit_dbus_meta_parse (l->data, interface, &error); if (iface) { cockpit_dbus_interface_info_push (self->interface_info, iface); g_dbus_interface_info_unref (iface); } else { cockpit_channel_fail (channel, "protocol-error", "%s", error->message); g_error_free (error); break; } } g_list_free (names); } static JsonObject * build_json_update (GHashTable *paths) { GHashTableIter i, j, k; GHashTable *interfaces; GHashTable *properties; const gchar *interface; const gchar *property; const gchar *path; JsonObject *notify; JsonObject *object; JsonObject *iface; GVariant *value; notify = json_object_new (); g_hash_table_iter_init (&i, paths); while (g_hash_table_iter_next (&i, (gpointer *)&path, (gpointer *)&interfaces)) { object = json_object_new (); g_hash_table_iter_init (&j, interfaces); while (g_hash_table_iter_next (&j, (gpointer *)&interface, (gpointer *)&properties)) { if (properties == NULL) { json_object_set_null_member (object, interface); } else { iface = json_object_new (); g_hash_table_iter_init (&k, properties); while (g_hash_table_iter_next (&k, (gpointer *)&property, (gpointer *)&value)) json_object_set_member (iface, property, build_json (value, NULL)); json_object_set_object_member (object, interface, iface); } } json_object_set_object_member (notify, path, object); } return notify; } static void on_cache_update (CockpitDBusCache *cache, GHashTable *update, gpointer user_data) { CockpitDBusPeer *peer = user_data; JsonObject *object = json_object_new (); maybe_include_name (peer->dbus_json, object, peer->name); json_object_set_object_member (object, "notify", build_json_update (update)); send_json_object (peer->dbus_json, object); json_object_unref (object); } static void handle_dbus_watch (CockpitDBusJson *self, JsonObject *object) { CockpitDBusPeer *peer = NULL; const gchar *name; const gchar *path; const gchar *path_namespace; const gchar *interface; gboolean is_namespace = FALSE; const gchar *cookie; JsonNode *node; node = json_object_get_member (object, "watch"); g_return_if_fail (node != NULL); if (!parse_json_rule (self, node, &name, &path, &path_namespace, &interface, NULL, NULL)) return; if (path_namespace) { path = path_namespace; is_namespace = TRUE; } peer = ensure_peer (self, name); cockpit_dbus_cache_watch (peer->cache, path, is_namespace, interface); if (!path) path = "/"; /* Send back a reply when this has completed */ if (cockpit_json_get_string (object, "id", NULL, &cookie)) { object = json_object_new (); json_object_set_array_member (object, "reply", json_array_new ()); json_object_set_string_member (object, "id", cookie); cockpit_dbus_cache_poke (peer->cache, path, NULL); send_with_barrier (self, peer, object); json_object_unref (object); } } static void handle_dbus_unwatch (CockpitDBusJson *self, JsonObject *object) { CockpitDBusPeer *peer; const gchar *name; const gchar *path; const gchar *path_namespace; const gchar *interface; gboolean is_namespace = FALSE; JsonNode *node; node = json_object_get_member (object, "unwatch"); g_return_if_fail (node != NULL); if (!parse_json_rule (self, node, &name, &path, &path_namespace, &interface, NULL, NULL)) return; if (path_namespace) { path = path_namespace; is_namespace = TRUE; } peer = ensure_peer (self, name); cockpit_dbus_cache_unwatch (peer->cache, path, is_namespace, interface); } static GVariantType * calculate_reply_param_type (CockpitChannel *channel, GDBusMethodInvocation *invocation) { const GVariantType *arg_types[256]; const GDBusMethodInfo *method_info; guint n; method_info = g_dbus_method_invocation_get_method_info (invocation); if (method_info == NULL) { cockpit_channel_fail (channel, "protocol-error", "reply argument types for signal %s %s unknown", g_dbus_method_invocation_get_interface_name (invocation), g_dbus_method_invocation_get_method_name (invocation)); return NULL; } else if (method_info->out_args) { for (n = 0; method_info->out_args[n] != NULL; n++) { /* DBus places a hard limit of 255 on signature length. * therefore number of args must be less than 256. */ if (n >= G_N_ELEMENTS (arg_types)) g_return_val_if_reached (NULL); arg_types[n] = G_VARIANT_TYPE (method_info->out_args[n]->signature); if G_UNLIKELY (arg_types[n] == NULL) g_return_val_if_reached (NULL); } } else { n = 0; } return g_variant_type_new_tuple (arg_types, n); } static void handle_dbus_reply (CockpitDBusJson *self, JsonObject *object) { CockpitChannel *channel = COCKPIT_CHANNEL (self); gpointer invocation = NULL; GVariantType *param_type; const gchar *cookie; GVariant *parameters; GError *error = NULL; JsonNode *args; JsonNode *node; node = json_object_get_member (object, "reply"); g_return_if_fail (node != NULL); if (!JSON_NODE_HOLDS_ARRAY (node)) { cockpit_channel_fail (channel, "protocol-error", "incorrect \"reply\" field in dbus command"); } else if (json_array_get_length (json_node_get_array (node)) < 1) { cockpit_channel_fail (channel, "protocol-error", "missing values in dbus method \"reply\""); } else if (!cockpit_json_get_string (object, "id", NULL, &cookie)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"id\" in dbus method reply"); } else if (cookie == NULL) { cockpit_channel_fail (channel, "protocol-error", "missing \"id\" in dbus method reply"); } else if (!self->invocations || !g_hash_table_lookup_extended (self->invocations, cookie, NULL, &invocation)) { cockpit_channel_fail (channel, "protocol-error", "unknown \"id\" in dbus method reply: %s", cookie); } else { param_type = calculate_reply_param_type (channel, invocation); args = json_array_get_element (json_node_get_array (node), 0); parameters = parse_json (args, param_type, &error); g_variant_type_free (param_type); if (error) { /* The invocation will be cleaned up by our dispose function */ cockpit_channel_fail (channel, "protocol-error", "invalid argument in dbus method reply: %s", error->message); g_error_free (error); } else { /* Reference to parameters is consumed */ g_dbus_method_invocation_return_value (invocation, parameters); g_hash_table_remove (self->invocations, cookie); } } } static gboolean parse_json_error (CockpitChannel *channel, JsonNode *node, const gchar **error_name, const gchar **error_message) { JsonArray *array; JsonNode *nested; JsonArray *params; guint length; g_assert (error_name != NULL); g_assert (error_message != NULL); *error_name = NULL; *error_message = NULL; if (!JSON_NODE_HOLDS_ARRAY (node)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"error\" field in dbus command"); return FALSE; } array = json_node_get_array (node); length = json_array_get_length (array); if (length < 1) { cockpit_channel_fail (channel, "protocol-error", "missing \"error\" error name in dbus command"); return FALSE; } *error_name = array_string_element (array, 0); if (!*error_name) { cockpit_channel_fail (channel, "protocol-error", "invalid \"error\" error name in dbus command"); return FALSE; } if (length >= 2) { nested = json_array_get_element (array, 1); if (!JSON_NODE_HOLDS_ARRAY (nested)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"error\" parameters in dbus command"); return FALSE; } params = json_node_get_array (nested); if (json_array_get_length (params) > 0) *error_message = array_string_element (params, 0); } if (!*error_message) *error_message = *error_name; return TRUE; } static void handle_dbus_error (CockpitDBusJson *self, JsonObject *object) { CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *error_message = NULL; const gchar *error_name = NULL; const gchar *cookie = NULL; gpointer invocation = NULL; JsonNode *node; node = json_object_get_member (object, "error"); g_return_if_fail (node != NULL); if (!JSON_NODE_HOLDS_ARRAY (node)) { cockpit_channel_fail (channel, "protocol-error", "incorrect \"error\" field in dbus command"); } else if (!parse_json_error (channel, node, &error_name, &error_message)) { /* Fail */ } else if (!cockpit_json_get_string (object, "id", NULL, &cookie)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"id\" in dbus method error"); } else if (cookie == NULL) { cockpit_channel_fail (channel, "protocol-error", "missing \"id\" in dbus method error"); } else if (!self->invocations || !g_hash_table_lookup_extended (self->invocations, cookie, NULL, &invocation)) { cockpit_channel_fail (channel, "protocol-error", "unknown \"id\" in dbus method error: %s", cookie); } else { g_dbus_method_invocation_return_dbus_error (invocation, error_name, error_message); g_hash_table_remove (self->invocations, cookie); } } static void on_method_invocation (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { CockpitDBusJson *self = user_data; GDBusMessageFlags flags; GDBusMessage *message; gchar *cookie = NULL; JsonObject *object; JsonArray *call; VariantContext context = { NULL }; message = g_dbus_method_invocation_get_message (invocation); flags = g_dbus_message_get_flags (message); object = json_object_new (); call = json_array_new (); json_array_add_string_element (call, object_path); json_array_add_string_element (call, interface_name); json_array_add_string_element (call, method_name); json_array_add_element (call, build_json (parameters, &context)); json_object_set_array_member (object, "call", call); if (!(flags & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED)) { g_assert (self->invocations != NULL); cookie = g_strdup_printf ("%d", self->last_invocation++); g_hash_table_insert (self->invocations, cookie, g_object_ref (invocation)); json_object_set_string_member (object, "id", cookie); } if (sender) json_object_set_string_member (object, "name", sender); send_json_object (self, object); json_object_unref (object); } static gboolean parse_json_publish (CockpitChannel *channel, JsonNode *node, const gchar **object_path, const gchar **interface_name) { JsonArray *array; g_assert (object_path != NULL); g_assert (interface_name != NULL); *object_path = NULL; *interface_name = NULL; if (!JSON_NODE_HOLDS_ARRAY (node)) { cockpit_channel_fail (channel, "protocol-error", "invalid publish field in dbus command"); return FALSE; } array = json_node_get_array (node); *object_path = array_string_element (array, 0); *interface_name = array_string_element (array, 1); if (!*object_path && !g_variant_is_object_path (*object_path)) { cockpit_channel_fail (channel, "protocol-error", "publish dbus object path is not valid: %s", *object_path); } else if (!*interface_name && !g_dbus_is_interface_name (*interface_name)) { cockpit_channel_fail (channel, "protocol-error", "publish dbus interface name is not valid: %s", *interface_name); } else { return TRUE; } return FALSE; } static void handle_dbus_publish (CockpitDBusJson *self, JsonObject *object) { CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *interface_name = NULL; const gchar *object_path = NULL; GHashTable *registered = NULL; const gchar *cookie = NULL; GDBusInterfaceInfo *iface; guint registration_id; GError *error = NULL; JsonNode *node; gchar *key; static const GDBusInterfaceVTable vtable = { on_method_invocation, NULL, NULL /* Property access handled as invocations */ }; node = json_object_get_member (object, "publish"); g_return_if_fail (node != NULL); if (!parse_json_publish (channel, node, &object_path, &interface_name)) return; iface = g_hash_table_lookup (self->interface_info, interface_name); if (!iface) { cockpit_channel_fail (channel, "protocol-error", "no \"meta\" introspection data available for interface: %s", interface_name); return; } /* Start up the necessary hash tables for exporting objects */ registered = g_object_get_data (G_OBJECT (self->connection), "registered-objects"); if (!registered) { registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_object_set_data_full (G_OBJECT (self->connection), "registered-objects", registered, (GDestroyNotify)g_hash_table_unref); } if (!self->registered) self->registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!self->invocations) self->invocations = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); /* If there's an old object registered here, unregister it */ key = g_strdup_printf ("%s\n%s", object_path, interface_name); registration_id = GPOINTER_TO_UINT (g_hash_table_lookup (registered, key)); if (registration_id) g_dbus_connection_unregister_object (self->connection, registration_id); /* Register the object */ registration_id = g_dbus_connection_register_object (self->connection, object_path, iface, &vtable, self, NULL, &error); if (error) { cockpit_channel_fail (channel, "protocol-error", "could not publish interface %s at %s: %s", interface_name, object_path, error->message); g_error_free (error); g_free (key); return; } /* All done mark this so we're able to later unpublish */ g_hash_table_insert (registered, g_strdup (key), GUINT_TO_POINTER (registration_id)); g_hash_table_insert (self->registered, key, GUINT_TO_POINTER (registration_id)); /* Caller wants a reply to know when published */ if (cockpit_json_get_string (object, "id", NULL, &cookie)) { object = json_object_new (); json_object_set_array_member (object, "reply", json_array_new ()); json_object_set_string_member (object, "id", cookie); send_json_object (self, object); json_object_unref (object); } } static void handle_dbus_unpublish (CockpitDBusJson *self, JsonObject *object) { CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *interface_name = NULL; const gchar *object_path = NULL; GHashTable *registered; JsonNode *node; gchar *key; guint id = 0; node = json_object_get_member (object, "unpublish"); g_return_if_fail (node != NULL); if (!parse_json_publish (channel, node, &object_path, &interface_name)) return; key = g_strdup_printf ("%s\n%s", object_path, interface_name); if (self->registered) { id = GPOINTER_TO_UINT (g_hash_table_lookup (self->registered, key)); g_hash_table_remove (self->registered, key); /* A per connection list of all interfaces and objects registered */ registered = g_object_get_data (G_OBJECT (self->connection), "registered-objects"); g_hash_table_remove (registered, key); } g_free (key); if (id != 0) g_dbus_connection_unregister_object (self->connection, id); } static void cockpit_dbus_json_recv (CockpitChannel *channel, GBytes *message) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (channel); GError *error = NULL; JsonObject *object = NULL; object = cockpit_json_parse_bytes (message, &error); if (!object) { cockpit_channel_fail (channel, "protocol-error", "failed to parse dbus request: %s", error->message); g_clear_error (&error); return; } if (json_object_has_member (object, "call")) handle_dbus_call (self, object); else if (json_object_has_member (object, "signal")) handle_dbus_signal (self, object); else if (json_object_has_member (object, "add-match")) handle_dbus_add_match (self, object); else if (json_object_has_member (object, "remove-match")) handle_dbus_remove_match (self, object); else if (json_object_has_member (object, "watch")) handle_dbus_watch (self, object); else if (json_object_has_member (object, "unwatch")) handle_dbus_unwatch (self, object); else if (json_object_has_member (object, "meta")) handle_dbus_meta (self, object); else if (json_object_has_member (object, "error")) handle_dbus_error (self, object); else if (json_object_has_member (object, "reply")) handle_dbus_reply (self, object); else if (json_object_has_member (object, "publish")) handle_dbus_publish (self, object); else if (json_object_has_member (object, "unpublish")) handle_dbus_unpublish (self, object); else { cockpit_channel_fail (channel, "protocol-error", "got unsupported dbus command"); } json_object_unref (object); } static void on_signal_message (GDBusConnection *connection, const gchar *sender, const gchar *path, const gchar *interface, const gchar *signal, GVariant *parameters, gpointer user_data) { /* * HACK: There is no way we can access the original GDBusMessage. This * means things like flags and byte order are lost here. * * We cannot use a GDBusMessageFilterFunction and use that to subsribe * to signals, because then the ordering guarantees are out the window. */ CockpitDBusPeer *peer = user_data; const gchar *arg0 = NULL; JsonObject *object; /* Unfortunately we also have to recalculate this */ if (parameters && g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE) && g_variant_n_children (parameters) > 0) { GVariant *item; item = g_variant_get_child_value (parameters, 0); if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING)) arg0 = g_variant_get_string (item, NULL); g_variant_unref (item); } if (cockpit_dbus_rules_match (peer->rules, path, interface, signal, arg0)) { object = build_json_signal (path, interface, signal, parameters); cockpit_dbus_cache_poke (peer->cache, path, interface); maybe_include_name (peer->dbus_json, object, peer->name); send_with_barrier (peer->dbus_json, peer, object); json_object_unref (object); } } static void cockpit_dbus_json_closed (CockpitChannel *channel, const gchar *problem) { /* When closed disconnect from everything */ g_object_run_dispose (G_OBJECT (channel)); } static void cockpit_dbus_json_init (CockpitDBusJson *self) { self->cancellable = g_cancellable_new (); self->peers = g_hash_table_new (g_str_hash, g_str_equal); self->interface_info = cockpit_dbus_interface_info_new (); } static void send_owned (CockpitDBusJson *self, const gchar *name, const gchar *owner) { JsonObject *object; object = json_object_new (); maybe_include_name (self, object, name); json_object_set_string_member (object, "owner", owner); send_json_object (self, object); json_object_unref (object); } static void send_ready (CockpitDBusJson *self) { CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *unique_name = NULL; JsonObject *message; message = json_object_new (); unique_name = g_dbus_connection_get_unique_name (self->connection); if (unique_name) json_object_set_string_member (message, "unique-name", unique_name); cockpit_channel_ready (channel, message); json_object_unref (message); } static void on_name_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (user_data); g_object_ref (self); if (!self->default_appeared) { self->default_appeared = TRUE; send_ready (self); } send_owned (self, name, name_owner); g_object_unref (self); } static void on_name_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (self); send_owned (self, name, NULL); if (!G_IS_DBUS_CONNECTION (connection) || g_dbus_connection_is_closed (connection)) cockpit_channel_close (channel, "disconnected"); else if (!self->default_appeared) cockpit_channel_close (channel, "not-found"); } static CockpitDBusPeer * ensure_peer (CockpitDBusJson *self, const gchar *name) { CockpitDBusPeer *peer; if (!name) name = self->default_name; peer = g_hash_table_lookup (self->peers, name ? name : ""); if (!peer) { peer = g_new0 (CockpitDBusPeer, 1); peer->name = g_strdup (name); peer->dbus_json = self; peer->cache = cockpit_dbus_cache_new (self->connection, name, self->logname, self->interface_info); peer->meta_sig = g_signal_connect (peer->cache, "meta", G_CALLBACK (on_cache_meta), peer); peer->update_sig = g_signal_connect (peer->cache, "update", G_CALLBACK (on_cache_update), peer); peer->rules = cockpit_dbus_rules_new (); peer->subscribe_id = g_dbus_connection_signal_subscribe (self->connection, name, NULL, /* interface */ NULL, /* member */ NULL, /* object_path */ NULL, /* arg0 */ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, on_signal_message, peer, NULL); g_hash_table_insert (self->peers, peer->name ? peer->name : "", peer); } return peer; } static void subscribe_and_cache (CockpitDBusJson *self) { g_dbus_connection_set_exit_on_close (self->connection, FALSE); if (self->default_name || self->bus_type == G_BUS_TYPE_NONE) ensure_peer (self, self->default_name); } static void process_connection (CockpitDBusJson *self, GError *error) { CockpitChannel *channel = COCKPIT_CHANNEL (self); GBusNameWatcherFlags flags; if (!self->connection) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || g_cancellable_is_cancelled (self->cancellable)) { g_debug ("%s", error->message); } else { cockpit_channel_fail (channel, "internal-error", "%s", error->message); } g_error_free (error); } else { /* Yup, we don't want this */ g_dbus_connection_set_exit_on_close (self->connection, FALSE); if (self->default_name) { flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START; self->default_watch = g_bus_watch_name_on_connection (self->connection, self->default_name, flags, on_name_appeared, on_name_vanished, self, NULL); self->default_watched = TRUE; subscribe_and_cache (self); } else { subscribe_and_cache (self); send_ready (self); } } } static void group_connections_weak_notify (gpointer data, GObject *where_the_object_was) { const gchar *key = data; if (group_connections) { g_hash_table_remove (group_connections, key); if (g_hash_table_size (group_connections) == 0) g_clear_pointer (&group_connections, g_hash_table_unref); } } static void on_connection_ready (GObject *source, GAsyncResult *result, gpointer user_data) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (user_data); GError *error = NULL; self->connection = g_dbus_connection_new_for_address_finish (result, &error); process_connection (self, error); g_object_unref (self); } static void on_shared_connection_ready (GObject *source, GAsyncResult *result, gpointer user_data) { gchar *key = user_data; GDBusConnection *connection; GError *error = NULL; GPtrArray *subscribers; connection = g_dbus_connection_new_for_address_finish (result, &error); /* * Notify all CockpitDBusJson objects from the same group that were * also waiting for this connection */ subscribers = g_hash_table_lookup (pending_group_connections, key); for (guint i = 0; i < subscribers->len; i++) { CockpitDBusJson *channel = g_ptr_array_index (subscribers, i); if (connection) channel->connection = g_object_ref (connection); process_connection (channel, error); } g_hash_table_remove (pending_group_connections, key); if (g_hash_table_size (pending_group_connections) == 0) g_clear_pointer (&pending_group_connections, g_hash_table_unref); if (connection) { /* * Ensure that the key is removed when the last CockpitDBusJson * object in its group drops the connection. group_connections * takes ownership of 'key'. */ g_object_weak_ref (G_OBJECT (connection), group_connections_weak_notify, key); g_hash_table_insert (group_connections, key, connection); g_object_unref (connection); } else { g_free (key); } } static void cockpit_dbus_json_prepare (CockpitChannel *channel) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (channel); JsonObject *options; const gchar *bus; const gchar *address; const gchar *group; gboolean internal = FALSE; COCKPIT_CHANNEL_CLASS (cockpit_dbus_json_parent_class)->prepare (channel); options = cockpit_channel_get_options (channel); if (!cockpit_json_get_string (options, "bus", NULL, &bus)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"bus\" option in dbus channel"); return; } if (!cockpit_json_get_string (options, "address", NULL, &address)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"address\" option in dbus channel"); return; } if (!cockpit_json_get_string (options, "group", "default", &group)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"group\" option in dbus channel"); return; } /* * The default bus is the "user" bus which doesn't exist in many * places yet, so use the session bus for now. */ self->bus_type = G_BUS_TYPE_SESSION; if (bus == NULL || g_str_equal (bus, "system")) { self->bus_type = G_BUS_TYPE_SYSTEM; } else if (g_str_equal (bus, "session") || g_str_equal (bus, "user")) { self->bus_type = G_BUS_TYPE_SESSION; } else if (g_str_equal (bus, "none")) { self->bus_type = G_BUS_TYPE_NONE; if (address == NULL || g_str_equal (address, "internal")) internal = TRUE; } else if (g_str_equal (bus, "internal")) { self->bus_type = G_BUS_TYPE_NONE; internal = TRUE; } else { cockpit_channel_fail (channel, "protocol-error", "invalid \"bus\" option in dbus channel: %s", bus); return; } if (!internal && !cockpit_dbus_json_allow_external) { cockpit_channel_close (channel, "not-supported"); return; } /* An internal peer to peer connection to cockpit-bridge */ if (internal) { if (!cockpit_json_get_null (options, "name", NULL)) { cockpit_channel_fail (channel, "protocol-error", "do not specify \"name\" option in dbus channel when \"internal\""); return; } self->connection = cockpit_dbus_internal_client (); if (self->connection == NULL) { cockpit_channel_fail (channel, "internal-error", "no internal DBus connection"); return; } self->default_name = cockpit_dbus_internal_name (); self->logname = "internal"; subscribe_and_cache (self); send_ready (self); } else { GDBusConnectionFlags flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT; if (!cockpit_json_get_string (options, "name", NULL, &self->default_name)) { self->default_name = NULL; if (!cockpit_json_get_null (options, "name", NULL)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"name\" option in dbus channel"); return; } } else if (self->default_name != NULL && !g_dbus_is_name (self->default_name)) { cockpit_channel_fail (channel, "protocol-error", "bad \"name\" option in dbus channel: %s", self->default_name); return; } if (self->bus_type == G_BUS_TYPE_NONE && !g_dbus_is_address (address)) { cockpit_channel_fail (channel, "protocol-error", "bad \"address\" option in dbus channel: %s", address); return; } if (self->default_name != NULL) self->logname = self->default_name; else if (address != NULL) self->logname = address; else self->logname = bus; if (self->bus_type == G_BUS_TYPE_NONE) { if (self->default_name) flags = flags | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION; g_dbus_connection_new_for_address (address, flags, NULL, self->cancellable, on_connection_ready, g_object_ref (self)); } else { gchar *bus_address = NULL; gchar *key = NULL; GDBusConnection *connection = NULL; bus_address = g_dbus_address_get_for_bus_sync (self->bus_type, self->cancellable, NULL); key = g_strdup_printf ("%s:%s", group, bus_address); if (!group_connections) group_connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); else connection = g_hash_table_lookup (group_connections, key); if (connection) { self->connection = g_object_ref (connection); process_connection (self, NULL); } else { GPtrArray *subscribers = NULL; if (!pending_group_connections) { pending_group_connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); } else { subscribers = g_hash_table_lookup (pending_group_connections, key); } if (!subscribers) { subscribers = g_ptr_array_new_with_free_func (g_object_unref); g_hash_table_insert (pending_group_connections, g_strdup (key), subscribers); g_dbus_connection_new_for_address (bus_address, flags | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, NULL, self->cancellable, on_shared_connection_ready, g_strdup (key)); } g_ptr_array_add (subscribers, g_object_ref (self)); } g_free (key); g_free (bus_address); } } } static void cockpit_dbus_json_dispose (GObject *object) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (object); GHashTable *registered = NULL; CockpitDBusPeer *peer; GHashTableIter iter; gpointer value; gpointer key; GList *l; g_cancellable_cancel (self->cancellable); if (self->default_watched) { g_bus_unwatch_name (self->default_watch); self->default_watched = FALSE; } g_hash_table_iter_init (&iter, self->peers); while (g_hash_table_iter_next (&iter, NULL, &value)) { g_hash_table_iter_remove (&iter); peer = value; g_free (peer->name); if (peer->cache) { g_signal_handler_disconnect (peer->cache, peer->meta_sig); g_signal_handler_disconnect (peer->cache, peer->update_sig); g_object_run_dispose (G_OBJECT (peer->cache)); g_object_unref (peer->cache); } cockpit_dbus_rules_free (peer->rules); if (self->connection) g_dbus_connection_signal_unsubscribe (self->connection, peer->subscribe_id); g_free (peer); } /* Remove and cancel all pending invocations */ if (self->connection) registered = g_object_get_data (G_OBJECT (self->connection), "registered-objects"); if (self->registered) { g_hash_table_iter_init (&iter, self->registered); while (g_hash_table_iter_next (&iter, &key, &value)) { if (registered) g_hash_table_remove (registered, key); g_dbus_connection_unregister_object (self->connection, GPOINTER_TO_UINT (value)); g_hash_table_iter_remove (&iter); } } if (self->invocations) { g_hash_table_iter_init (&iter, self->invocations); while (g_hash_table_iter_next (&iter, NULL, &value)) { g_hash_table_iter_remove (&iter); g_dbus_method_invocation_return_dbus_error (value, "org.freedesktop.DBus.Error.Failed", "The DBus interface for this method has been disconnected"); } } if (self->fd_channel_ids) { while (!g_queue_is_empty (self->fd_channel_ids)) { gchar *id; id = (gchar *)g_queue_pop_head (self->fd_channel_ids); cockpit_pipe_channel_remove_internal_fd (id); g_free (id); } g_queue_free (self->fd_channel_ids); self->fd_channel_ids = NULL; } /* Divorce ourselves the outstanding calls */ for (l = self->active_calls; l != NULL; l = g_list_next (l)) ((CallData *)l->data)->dbus_json = NULL; g_list_free (self->active_calls); self->active_calls = NULL; G_OBJECT_CLASS (cockpit_dbus_json_parent_class)->dispose (object); } static void cockpit_dbus_json_finalize (GObject *object) { CockpitDBusJson *self = COCKPIT_DBUS_JSON (object); g_clear_object (&self->connection); g_hash_table_unref (self->interface_info); g_hash_table_unref (self->peers); if (self->registered) g_hash_table_unref (self->registered); if (self->invocations) g_hash_table_unref (self->invocations); g_object_unref (self->cancellable); G_OBJECT_CLASS (cockpit_dbus_json_parent_class)->finalize (object); } static void cockpit_dbus_json_constructed (GObject *object) { const gchar *caps[] = { "address", NULL }; G_OBJECT_CLASS (cockpit_dbus_json_parent_class)->constructed (object); g_object_set (object, "capabilities", &caps, NULL); } static void cockpit_dbus_json_class_init (CockpitDBusJsonClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); CockpitChannelClass *channel_class = COCKPIT_CHANNEL_CLASS (klass); gobject_class->dispose = cockpit_dbus_json_dispose; gobject_class->finalize = cockpit_dbus_json_finalize; gobject_class->constructed = cockpit_dbus_json_constructed; channel_class->prepare = cockpit_dbus_json_prepare; channel_class->recv = cockpit_dbus_json_recv; channel_class->closed = cockpit_dbus_json_closed; } /** * cockpit_dbus_json_open: * @transport: transport to send messages on * @channel_id: the channel id * @dbus_service: the DBus service name to talk to * * This function is mainly used by tests. The normal way to open * channels is cockpit_channel_open(). * * Guarantee: channel will not close immediately, even on invalid input. * * Returns: (transfer full): a new channel */ CockpitChannel * cockpit_dbus_json_open (CockpitTransport *transport, const gchar *channel_id, const gchar *dbus_service) { CockpitChannel *channel; JsonObject *options; g_return_val_if_fail (channel_id != NULL, NULL); options = json_object_new (); json_object_set_string_member (options, "bus", "session"); json_object_set_string_member (options, "service", dbus_service); json_object_set_string_member (options, "payload", "dbus-json3"); channel = g_object_new (COCKPIT_TYPE_DBUS_JSON, "transport", transport, "id", channel_id, "options", options, NULL); json_object_unref (options); return channel; }