static void cockpit_channel_real_prepare (CockpitChannel *channel) { CockpitChannel *self = COCKPIT_CHANNEL (channel); JsonObject *options; const gchar *binary; options = cockpit_channel_get_options (self); if (!cockpit_channel_ensure_capable (self, options)) return; if (G_OBJECT_TYPE (channel) == COCKPIT_TYPE_CHANNEL) { cockpit_channel_close (channel, "not-supported"); return; } if (!cockpit_json_get_string (options, "binary", NULL, &binary)) { cockpit_channel_fail (self, "protocol-error", "channel has invalid \"binary\" option"); } else if (binary != NULL) { self->priv->binary_ok = TRUE; if (!g_str_equal (binary, "raw")) { cockpit_channel_fail (self, "protocol-error", "channel has invalid \"binary\" option: %s", binary); } } }
static gboolean parse_option_file_or_data (CockpitChannel *self, JsonObject *options, const gchar *option, const gchar **file, const gchar **data) { JsonObject *object; JsonNode *node; g_assert (file != NULL); g_assert (data != NULL); node = json_object_get_member (options, option); if (!node) { *file = NULL; *data = NULL; return TRUE; } if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (self, "protocol-error", "invalid \"%s\" tls option for channel", option); return FALSE; } object = json_node_get_object (node); if (!cockpit_json_get_string (object, "file", NULL, file)) { cockpit_channel_fail (self, "protocol-error", "invalid \"file\" %s option for channel", option); } else if (!cockpit_json_get_string (object, "data", NULL, data)) { cockpit_channel_fail (self, "protocol-error", "invalid \"data\" %s option for channel", option); } else if (!*file && !*data) { cockpit_channel_fail (self, "not-supported", "missing or unsupported \"%s\" option for channel", option); } else if (*file && *data) { cockpit_channel_fail (self, "protocol-error", "cannot specify both \"file\" and \"data\" in \"%s\" option for channel", option); } else { return TRUE; } return FALSE; }
static void process_control (CockpitChannel *self, const gchar *command, JsonObject *options) { CockpitChannelClass *klass; const gchar *problem; if (g_str_equal (command, "close")) { g_debug ("close channel %s", self->priv->id); if (!cockpit_json_get_string (options, "problem", NULL, &problem)) problem = NULL; cockpit_channel_close (self, problem); return; } if (g_str_equal (command, "done")) { if (self->priv->received_done) cockpit_channel_fail (self, "protocol-error", "channel received second done"); else self->priv->received_done = TRUE; } klass = COCKPIT_CHANNEL_GET_CLASS (self); if (klass->control) (klass->control) (self, command, options); }
static gboolean parse_transfer_encoding (CockpitHttpStream *self, CockpitChannel *channel, GHashTable *headers) { const gchar *header; header = g_hash_table_lookup (headers, "Transfer-Encoding"); if (header == NULL) { self->response_chunked = FALSE; return TRUE; } if (!g_str_equal (header, "chunked")) { cockpit_channel_fail (channel, "protocol-error", "%s: received unsupported Transfer-Encoding in HTTP response: %s", self->name, header); return FALSE; } self->response_chunked = TRUE; g_debug ("%s: chunked encoding", self->name); return TRUE; }
static void on_stream_close (CockpitStream *stream, const gchar *problem, gpointer user_data) { CockpitHttpStream *self = user_data; CockpitChannel *channel = user_data; self->keep_alive = FALSE; if (self->state != FINISHED) { if (problem) { cockpit_channel_close (channel, problem); } else if (self->state == RELAY_DATA && !self->response_chunked && self->response_length <= 0) { g_debug ("%s: end of stream is end of data", self->name); cockpit_channel_close (channel, NULL); } else { cockpit_channel_fail (channel, "protocol-error", "%s: received truncated HTTP response", self->name); } } }
static gboolean load_pem_contents (CockpitChannel *self, const gchar *filename, const gchar *option, GString *pem) { GError *error = NULL; gchar *contents = NULL; gsize len; if (!g_file_get_contents (filename, &contents, &len, &error)) { cockpit_channel_fail (self, "internal-error", "couldn't load \"%s\" file: %s: %s", option, filename, error->message); g_clear_error (&error); return FALSE; } else { g_string_append_len (pem, contents, len); g_string_append_c (pem, '\n'); g_free (contents); return TRUE; } }
static void cockpit_fswatch_recv (CockpitChannel *channel, GBytes *message) { cockpit_channel_fail (channel, "protocol-error", "received unexpected message in fswatch channel"); }
static void on_stream_read (CockpitStream *stream, GByteArray *buffer, gboolean end_of_data, gpointer user_data) { CockpitHttpStream *self = user_data; CockpitChannel *channel = user_data; gboolean ret; g_object_ref (self); if (self->state < RELAY_REQUEST) { if (buffer->len != 0) { cockpit_channel_fail (channel, "protocol-error", "%s: received data before HTTP request was sent", self->name); } } else if (self->state < RELAY_DATA) { /* Parse headers */ if (relay_headers (self, channel, buffer)) { self->state = RELAY_DATA; } else if (end_of_data) { cockpit_channel_fail (channel, "protocol-error", "%s: received truncated HTTP response", self->name); } } while (self->state == RELAY_DATA) { if (self->response_chunked) ret = relay_chunked (self, channel, buffer); else if (self->response_length >= 0) ret = relay_length (self, channel, buffer); else ret = relay_all (self, channel, buffer); if (!ret) break; } g_object_unref (self); }
static gboolean cockpit_channel_ensure_capable (CockpitChannel *channel, JsonObject *options) { gchar **capabilities = NULL; JsonObject *close_options = NULL; // owned by channel gboolean missing = FALSE; gboolean ret = FALSE; gint len; gint i; if (!cockpit_json_get_strv (options, "capabilities", NULL, &capabilities)) { cockpit_channel_fail (channel, "protocol-error", "got invalid capabilities field in open message"); goto out; } if (!capabilities) { ret = TRUE; goto out; } len = g_strv_length (capabilities); for (i = 0; i < len; i++) { if (channel->priv->capabilities == NULL || !strv_contains(channel->priv->capabilities, capabilities[i])) { g_message ("%s: unsupported capability required: %s", channel->priv->id, capabilities[i]); missing = TRUE; } } if (missing) { JsonArray *arr = json_array_new (); // owned by closed options if (channel->priv->capabilities != NULL) { len = g_strv_length (channel->priv->capabilities); for (i = 0; i < len; i++) json_array_add_string_element (arr, channel->priv->capabilities[i]); } close_options = cockpit_channel_close_options (channel); json_object_set_array_member (close_options, "capabilities", arr); cockpit_channel_close (channel, "not-supported"); } ret = !missing; out: g_free (capabilities); return ret; }
static void cockpit_web_socket_stream_prepare (CockpitChannel *channel) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel); CockpitConnectable *connectable = NULL; JsonObject *options; const gchar *path; COCKPIT_CHANNEL_CLASS (cockpit_web_socket_stream_parent_class)->prepare (channel); if (self->closed) goto out; connectable = cockpit_channel_parse_stream (channel); if (!connectable) goto out; options = cockpit_channel_get_options (channel); if (!cockpit_json_get_string (options, "path", NULL, &path)) { cockpit_channel_fail (channel, "protocol-error", "%s: bad \"path\" field in WebSocket stream request", self->origin); goto out; } else if (path == NULL || path[0] != '/') { cockpit_channel_fail (channel, "protocol-error", "%s: invalid or missing \"path\" field in WebSocket stream request", self->origin); goto out; } self->url = g_strdup_printf ("%s://%s%s", connectable->tls ? "wss" : "ws", connectable->name, path); self->origin = g_strdup_printf ("%s://%s", connectable->tls ? "https" : "http", connectable->name); /* Parsed elsewhere */ self->binary = json_object_has_member (options, "binary"); cockpit_connect_stream_full (connectable, NULL, on_socket_connect, g_object_ref (self)); out: if (connectable) cockpit_connectable_unref (connectable); }
static void cockpit_fswatch_prepare (CockpitChannel *channel) { CockpitFswatch *self = COCKPIT_FSWATCH (channel); JsonObject *options; GError *error = NULL; const gchar *path; COCKPIT_CHANNEL_CLASS (cockpit_fswatch_parent_class)->prepare (channel); options = cockpit_channel_get_options (channel); if (!cockpit_json_get_string (options, "path", NULL, &path)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"path\" option for fswatch channel"); goto out; } else if (path == NULL || *path == 0) { cockpit_channel_fail (channel, "protocol-error", "missing \"path\" option for fswatch channel"); goto out; } GFile *file = g_file_new_for_path (path); GFileMonitor *monitor = g_file_monitor (file, 0, NULL, &error); g_object_unref (file); if (monitor == NULL) { cockpit_channel_fail (channel, "internal-error", "%s: %s", self->path, error->message); goto out; } self->monitor = monitor; self->sig_changed = g_signal_connect (self->monitor, "changed", G_CALLBACK (on_changed), self); cockpit_channel_ready (channel, NULL); out: g_clear_error (&error); }
static void cockpit_channel_real_prepare (CockpitChannel *channel) { CockpitChannel *self = COCKPIT_CHANNEL (channel); JsonObject *options; const gchar *binary; options = cockpit_channel_get_options (self); if (!cockpit_channel_ensure_capable (self, options)) return; if (G_OBJECT_TYPE (channel) == COCKPIT_TYPE_CHANNEL) { cockpit_channel_close (channel, "not-supported"); return; } if (!cockpit_json_get_string (options, "binary", NULL, &binary)) { cockpit_channel_fail (self, "protocol-error", "channel has invalid \"binary\" option"); } else if (binary != NULL) { self->priv->binary_ok = TRUE; if (!g_str_equal (binary, "raw")) { cockpit_channel_fail (self, "protocol-error", "channel has invalid \"binary\" option: %s", binary); } } /* * The default here, can change from FALSE to TRUE over time once we assume that all * cockpit-ws participants have been upgraded sufficiently. The default when we're * on the channel creation side is to handle flow control. */ if (!cockpit_json_get_bool (options, "flow-control", FALSE, &self->priv->flow_control)) { cockpit_channel_fail (self, "protocol-error", "channel has invalid \"flow-control\" option"); } }
static gboolean parse_content_length (CockpitHttpStream *self, CockpitChannel *channel, guint status, GHashTable *headers) { const gchar *header; guint64 value; gchar *end; if (status == 204) { self->response_length = 0; return TRUE; } header = g_hash_table_lookup (headers, "Content-Length"); if (header == NULL) { self->response_length = -1; return TRUE; } value = g_ascii_strtoull (header, &end, 10); if (end[0] != '\0') { cockpit_channel_fail (channel, "protocol-error", "%s: received invalid Content-Length in HTTP stream response", self->name); return FALSE; } else if (value > G_MAXSSIZE) { cockpit_channel_fail (channel, "protocol-error", "%s: received Content-Length that was too big", self->name); return FALSE; } self->response_length = value; g_debug ("%s: content length is %" G_GSSIZE_FORMAT, self->name, self->response_length); return TRUE; }
static void process_recv (CockpitChannel *self, GBytes *payload) { CockpitChannelClass *klass; if (self->priv->received_done) { cockpit_channel_fail (self, "protocol-error", "channel received message after done"); } else { klass = COCKPIT_CHANNEL_GET_CLASS (self); if (klass->recv) (klass->recv) (self, payload); } }
GSocketAddress * cockpit_channel_parse_address (CockpitChannel *self, gchar **possible_name) { GSocketConnectable *connectable; GSocketAddressEnumerator *enumerator; GSocketAddress *address; GError *error = NULL; gchar *name = NULL; connectable = parse_address (self, &name, NULL); if (!connectable) return NULL; /* This is sync, but realistically, it doesn't matter for current use cases */ enumerator = g_socket_connectable_enumerate (connectable); g_object_unref (connectable); address = g_socket_address_enumerator_next (enumerator, NULL, &error); g_object_unref (enumerator); if (error != NULL) { cockpit_channel_fail (self, "not-found", "couldn't find address: %s: %s", name, error->message); g_error_free (error); g_free (name); return NULL; } if (possible_name) *possible_name = name; else g_free (name); return address; }
static gboolean relay_chunked (CockpitHttpStream *self, CockpitChannel *channel, GByteArray *buffer) { GBytes *message; const gchar *data; const gchar *pos; guint64 size; gsize length; gsize beg; gchar *end; data = (const gchar *)buffer->data; length = buffer->len; pos = memchr (data, '\r', length); if (pos == NULL) return FALSE; /* want more data */ beg = (pos + 2) - data; if (length < beg) { /* have to have a least the ending chars */ return FALSE; /* want more data */ } size = g_ascii_strtoull (data, &end, 16); if (pos[1] != '\n' || end != pos) { cockpit_channel_fail (channel, "protocol-error", "%s: received invalid HTTP chunk", self->name); } else if (size > G_MAXSSIZE) { cockpit_channel_fail (channel, "protocol-error", "%s: received extremely large HTTP chunk", self->name); } else if (length < beg + size + 2) { return FALSE; /* want more data */ } else if (data[beg + size] != '\r' || data[beg + size + 1] != '\n') { cockpit_channel_fail (channel, "protocol-error", "%s: received invalid HTTP chunk data", self->name); } else if (size == 0) { /* All done, yay */ g_debug ("%s: received last chunk", self->name); cockpit_pipe_skip (buffer, beg + 2); cockpit_channel_close (channel, NULL); g_assert (self->state == FINISHED); } else { message = cockpit_pipe_consume (buffer, beg, size, 2); relay_data (channel, message); g_bytes_unref (message); return TRUE; } return TRUE; }
static void on_socket_connect (GObject *object, GAsyncResult *result, gpointer user_data) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *problem = "protocol-error"; gchar **protocols = NULL; GList *l, *names = NULL; GError *error = NULL; JsonObject *options; JsonObject *headers; const gchar *value; JsonNode *node; GIOStream *io; io = cockpit_connect_stream_finish (result, &error); if (error) { problem = cockpit_stream_problem (error, self->origin, "couldn't connect", cockpit_channel_close_options (channel)); cockpit_channel_close (channel, problem); goto out; } options = cockpit_channel_get_options (channel); if (!cockpit_json_get_strv (options, "protocols", NULL, &protocols)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"protocol\" value in WebSocket stream request", self->origin); goto out; } if (G_IS_TLS_CONNECTION (io)) { self->sig_accept_cert = g_signal_connect (G_TLS_CONNECTION (io), "accept-certificate", G_CALLBACK (on_rejected_certificate), self); } else { self->sig_accept_cert = 0; } self->client = web_socket_client_new_for_stream (self->url, self->origin, (const gchar **)protocols, io); node = json_object_get_member (options, "headers"); if (node) { if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"headers\" field in WebSocket stream request", self->origin); goto out; } headers = json_node_get_object (node); names = json_object_get_members (headers); for (l = names; l != NULL; l = g_list_next (l)) { node = json_object_get_member (headers, l->data); if (!node || !JSON_NODE_HOLDS_VALUE (node) || json_node_get_value_type (node) != G_TYPE_STRING) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header value in WebSocket stream request: %s", self->origin, (gchar *)l->data); goto out; } value = json_node_get_string (node); g_debug ("%s: sending header: %s %s", self->origin, (gchar *)l->data, value); web_socket_client_include_header (WEB_SOCKET_CLIENT (self->client), l->data, value); } } self->sig_open = g_signal_connect (self->client, "open", G_CALLBACK (on_web_socket_open), self); self->sig_message = g_signal_connect (self->client, "message", G_CALLBACK (on_web_socket_message), self); self->sig_closing = g_signal_connect (self->client, "closing", G_CALLBACK (on_web_socket_closing), self); self->sig_close = g_signal_connect (self->client, "close", G_CALLBACK (on_web_socket_close), self); self->sig_error = g_signal_connect (self->client, "error", G_CALLBACK (on_web_socket_error), self); problem = NULL; out: g_clear_error (&error); g_strfreev (protocols); if (io) g_object_unref (io); g_list_free (names); }
static void cockpit_http_stream_prepare (CockpitChannel *channel) { CockpitHttpStream *self = COCKPIT_HTTP_STREAM (channel); CockpitConnectable *connectable = NULL; const gchar *payload; const gchar *connection; JsonObject *options; const gchar *path; COCKPIT_CHANNEL_CLASS (cockpit_http_stream_parent_class)->prepare (channel); if (self->failed) goto out; options = cockpit_channel_get_options (channel); if (!cockpit_json_get_string (options, "connection", NULL, &connection)) { cockpit_channel_fail (channel, "protocol-error", "bad \"connection\" field in HTTP stream request"); goto out; } if (!cockpit_json_get_string (options, "path", "/", &path)) { cockpit_channel_fail (channel, "protocol-error", "bad \"path\" field in HTTP stream request"); goto out; } /* * In http-stream1 the headers are sent as first message. * In http-stream2 the headers are in a control message. */ if (cockpit_json_get_string (options, "payload", NULL, &payload) && payload && g_str_equal (payload, "http-stream1")) { self->headers_inline = TRUE; } self->client = cockpit_http_client_ensure (connection); if (!self->client->connectable || json_object_has_member (options, "unix") || json_object_has_member (options, "port") || json_object_has_member (options, "internal") || json_object_has_member (options, "tls") || json_object_has_member (options, "address")) { connectable = cockpit_connect_parse_stream (channel); if (!connectable) goto out; if (self->client->connectable) cockpit_connectable_unref (self->client->connectable); self->client->connectable = cockpit_connectable_ref (connectable); } self->name = g_strdup_printf ("%s://%s%s", self->client->connectable->tls ? "https" : "http", self->client->connectable->name, path); self->stream = cockpit_http_client_checkout (self->client); if (!self->stream) { self->stream = cockpit_stream_connect (self->name, self->client->connectable); self->sig_open = g_signal_connect (self->stream, "open", G_CALLBACK (on_stream_open), self); } /* Parsed elsewhere */ self->binary = json_object_has_member (options, "binary"); self->sig_read = g_signal_connect (self->stream, "read", G_CALLBACK (on_stream_read), self); self->sig_close = g_signal_connect (self->stream, "close", G_CALLBACK (on_stream_close), self); self->sig_rejected_cert = g_signal_connect (self->stream, "rejected-cert", G_CALLBACK (on_rejected_cert), self); /* Let the channel throttle the stream's input flow*/ cockpit_flow_throttle (COCKPIT_FLOW (self->stream), COCKPIT_FLOW (self)); /* Let the stream throtlte the channel peer's output flow */ cockpit_flow_throttle (COCKPIT_FLOW (channel), COCKPIT_FLOW (self->stream)); /* If not waiting for open */ if (!self->sig_open) cockpit_channel_ready (channel, NULL); out: if (connectable) cockpit_connectable_unref (connectable); }
static void send_http_request (CockpitHttpStream *self) { CockpitChannel *channel = COCKPIT_CHANNEL (self); JsonObject *options; gboolean had_host; gboolean had_encoding; const gchar *method; const gchar *path; GString *string = NULL; JsonNode *node; JsonObject *headers; const gchar *header; const gchar *value; GList *request = NULL; GList *names = NULL; GBytes *bytes; GList *l; gsize total; options = cockpit_channel_get_options (channel); /* * The checks we do here for token validity are just enough to be able * to format an HTTP response, without leaking across lines etc. */ if (!cockpit_json_get_string (options, "path", NULL, &path)) { cockpit_channel_fail (channel, "protocol-error", "%s: bad \"path\" field in HTTP stream request", self->name); goto out; } else if (path == NULL) { cockpit_channel_fail (channel, "protocol-error", "%s: missing \"path\" field in HTTP stream request", self->name); goto out; } else if (!cockpit_web_response_is_simple_token (path)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"path\" field in HTTP stream request", self->name); goto out; } if (!cockpit_json_get_string (options, "method", NULL, &method)) { cockpit_channel_fail (channel, "protocol-error", "%s: bad \"method\" field in HTTP stream request", self->name); goto out; } else if (method == NULL) { cockpit_channel_fail (channel, "protocol-error", "%s: missing \"method\" field in HTTP stream request", self->name); goto out; } else if (!cockpit_web_response_is_simple_token (method)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"method\" field in HTTP stream request", self->name); goto out; } g_debug ("%s: sending %s request", self->name, method); string = g_string_sized_new (128); g_string_printf (string, "%s %s HTTP/1.1\r\n", method, path); had_encoding = had_host = FALSE; node = json_object_get_member (options, "headers"); if (node) { if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"headers\" field in HTTP stream request", self->name); goto out; } headers = json_node_get_object (node); names = json_object_get_members (headers); for (l = names; l != NULL; l = g_list_next (l)) { header = l->data; if (!cockpit_web_response_is_simple_token (header)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header in HTTP stream request: %s", self->name, header); goto out; } node = json_object_get_member (headers, header); if (!node || !JSON_NODE_HOLDS_VALUE (node) || json_node_get_value_type (node) != G_TYPE_STRING) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header value in HTTP stream request: %s", self->name, header); goto out; } value = json_node_get_string (node); if (disallowed_header (header, value, self->binary)) { cockpit_channel_fail (channel, "protocol-error", "%s: disallowed header in HTTP stream request: %s", self->name, header); goto out; } if (!cockpit_web_response_is_header_value (value)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header value in HTTP stream request: %s", self->name, header); goto out; } g_string_append_printf (string, "%s: %s\r\n", (gchar *)l->data, value); g_debug ("%s: sending header: %s %s", self->name, (gchar *)l->data, value); if (g_ascii_strcasecmp (l->data, "Host") == 0) had_host = TRUE; if (g_ascii_strcasecmp (l->data, "Accept-Encoding") == 0) had_encoding = TRUE; } } if (!had_host) { g_string_append (string, "Host: "); g_string_append_uri_escaped (string, self->client->connectable->name, "[]!%$&()*+,-.:;=\\_~", FALSE); g_string_append (string, "\r\n"); } if (!had_encoding) g_string_append (string, "Accept-Encoding: identity\r\n"); if (!self->binary) g_string_append (string, "Accept-Charset: UTF-8\r\n"); request = g_list_reverse (self->request); self->request = NULL; /* Calculate how much data we have to send */ total = 0; for (l = request; l != NULL; l = g_list_next (l)) total += g_bytes_get_size (l->data); if (request || g_ascii_strcasecmp (method, "POST") == 0) g_string_append_printf (string, "Content-Length: %" G_GSIZE_FORMAT "\r\n", total); g_string_append (string, "\r\n"); bytes = g_string_free_to_bytes (string); string = NULL; cockpit_stream_write (self->stream, bytes); g_bytes_unref (bytes); /* Now send all the data */ for (l = request; l != NULL; l = g_list_next (l)) cockpit_stream_write (self->stream, l->data); out: g_list_free (names); g_list_free_full (request, (GDestroyNotify)g_bytes_unref); if (string) g_string_free (string, TRUE); }
static gboolean parse_cert_option_as_database (CockpitChannel *self, JsonObject *options, const gchar *option, GTlsDatabase **database) { gboolean temporary = FALSE; GError *error = NULL; gboolean ret = TRUE; const gchar *file; const gchar *data; gchar *path; gint fd; if (!parse_option_file_or_data (self, options, option, &file, &data)) return FALSE; if (file) { path = expand_filename (file); ret = TRUE; } else if (data) { temporary = TRUE; path = g_build_filename (g_get_user_runtime_dir (), "cockpit-bridge-cert-authority.XXXXXX", NULL); fd = g_mkstemp (path); if (fd < 0) { ret = FALSE; cockpit_channel_fail (self, "internal-error", "couldn't create temporary directory: %s: %s", path, g_strerror (errno)); } else { close (fd); if (!g_file_set_contents (path, data, -1, &error)) { cockpit_channel_fail (self, "internal-error", "couldn't write temporary data to: %s: %s", path, error->message); g_clear_error (&error); ret = FALSE; } } } else { /* Not specified */ *database = NULL; return TRUE; } if (ret) { *database = g_tls_file_database_new (path, &error); if (error) { cockpit_channel_fail (self, "internal-error", "couldn't load certificate data: %s: %s", path, error->message); g_clear_error (&error); ret = FALSE; } } /* Leave around when problem, for debugging */ if (temporary && ret == TRUE) g_unlink (path); g_free (path); return ret; }
static gboolean convert_metric_description (CockpitInternalMetrics *self, JsonNode *node, MetricInfo *info, int index) { CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *name; const gchar *units; if (json_node_get_node_type (node) == JSON_NODE_OBJECT) { if (!cockpit_json_get_string (json_node_get_object (node), "name", NULL, &name) || name == NULL) { cockpit_channel_fail (channel, "protocol-error", "invalid \"metrics\" option was specified (no name for metric %d)", index); return FALSE; } if (!cockpit_json_get_string (json_node_get_object (node), "units", NULL, &units)) { cockpit_channel_fail (channel, "protocol-error", "invalid units for metric %s (not a string)", name); return FALSE; } if (!cockpit_json_get_string (json_node_get_object (node), "derive", NULL, &info->derive)) { cockpit_channel_fail (channel, "protocol-error", "invalid derivation mode for metric %s (not a string)", name); return FALSE; } } else { cockpit_channel_fail (channel, "protocol-error", "invalid \"metrics\" option was specified (not an object for metric %d)", index); return FALSE; } MetricDescription *desc = find_metric_description (name); if (desc == NULL) { g_message ("unknown internal metric %s", name); } else { if (units && g_strcmp0 (desc->units, units) != 0) { cockpit_channel_fail (channel, "protocol-error", "%s has units %s, not %s", name, desc->units, units); return FALSE; } if (desc->instanced) info->instances = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); info->desc = desc; self->samplers |= desc->sampler; } return TRUE; }
static gboolean relay_headers (CockpitHttpStream *self, CockpitChannel *channel, GByteArray *buffer) { GHashTable *headers = NULL; gchar *version = NULL; gchar *reason = NULL; JsonObject *object; const gchar *data; JsonObject *heads; GHashTableIter iter; GBytes *message; gpointer key; gpointer value; guint status; gsize length; gssize offset; gssize offset2; data = (const gchar *)buffer->data; length = buffer->len; offset = web_socket_util_parse_status_line (data, length, &version, &status, &reason); if (offset == 0) return FALSE; /* want more data */ if (offset < 0) { cockpit_channel_fail (channel, "protocol-error", "%s: received response with bad HTTP status line", self->name); goto out; } offset2 = web_socket_util_parse_headers (data + offset, length - offset, &headers); if (offset2 == 0) return FALSE; /* want more data */ if (offset2 < 0) { cockpit_channel_fail (channel, "protocol-error", "%s: received response with bad HTTP headers", self->name); goto out; } g_debug ("%s: response: %u %s", self->name, status, reason); g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, &key, &value)) g_debug ("%s: header: %s %s", self->name, (gchar *)key, (gchar *)value); if (!parse_transfer_encoding (self, channel, headers) || !parse_content_length (self, channel, status, headers) || !parse_keep_alive (self, channel, version, headers)) goto out; cockpit_pipe_skip (buffer, offset + offset2); if (!self->binary) { g_hash_table_remove (headers, "Content-Length"); g_hash_table_remove (headers, "Range"); } g_hash_table_remove (headers, "Connection"); g_hash_table_remove (headers, "Transfer-Encoding"); /* Now serialize all the rest of this into JSON */ object = json_object_new (); json_object_set_int_member (object, "status", status); json_object_set_string_member (object, "reason", reason); heads = json_object_new(); g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, &key, &value)) json_object_set_string_member (heads, key, value); json_object_set_object_member (object, "headers", heads); if (self->headers_inline) { message = cockpit_json_write_bytes (object); cockpit_channel_send (channel, message, TRUE); g_bytes_unref (message); } else { cockpit_channel_control (channel, "response", object); } json_object_unref (object); out: if (headers) g_hash_table_unref (headers); g_free (version); g_free (reason); return TRUE; }
static gboolean parse_stream_options (CockpitChannel *self, CockpitConnectable *connectable) { gboolean ret = FALSE; GTlsCertificate *cert = NULL; GTlsDatabase *database = NULL; gboolean use_tls = FALSE; GError *error = NULL; GString *pem = NULL; JsonObject *options; JsonNode *node; /* No validation for local servers by default */ gboolean validate = !connectable->local; node = json_object_get_member (self->priv->open_options, "tls"); if (node && !JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (self, "protocol-error", "invalid \"tls\" option for channel"); goto out; } else if (node) { options = json_node_get_object (node); use_tls = TRUE; /* * The only function in GLib to parse private keys takes * them in PEM concatenated form. This is a limitation of GLib, * rather than concatenated form being a decent standard for * certificates and keys. So build a combined PEM as expected by * GLib here. */ pem = g_string_sized_new (8192); if (!parse_cert_option_as_pem (self, options, "certificate", pem)) goto out; if (pem->len) { if (!parse_cert_option_as_pem (self, options, "key", pem)) goto out; cert = g_tls_certificate_new_from_pem (pem->str, pem->len, &error); if (error != NULL) { cockpit_channel_fail (self, "internal-error", "invalid \"certificate\" or \"key\" content: %s", error->message); g_error_free (error); goto out; } } if (!parse_cert_option_as_database (self, options, "authority", &database)) goto out; if (!cockpit_json_get_bool (options, "validate", validate, &validate)) { cockpit_channel_fail (self, "protocol-error", "invalid \"validate\" option"); goto out; } } ret = TRUE; out: if (ret) { connectable->tls = use_tls; connectable->tls_cert = cert; cert = NULL; if (database) { connectable->tls_database = database; connectable->tls_flags = G_TLS_CERTIFICATE_VALIDATE_ALL; if (!validate) connectable->tls_flags &= ~(G_TLS_CERTIFICATE_INSECURE | G_TLS_CERTIFICATE_BAD_IDENTITY); database = NULL; } else { if (validate) connectable->tls_flags = G_TLS_CERTIFICATE_VALIDATE_ALL; else connectable->tls_flags = G_TLS_CERTIFICATE_GENERIC_ERROR; } } if (pem) g_string_free (pem, TRUE); if (cert) g_object_unref (cert); if (database) g_object_unref (database); return ret; }
static void cockpit_internal_metrics_prepare (CockpitChannel *channel) { CockpitInternalMetrics *self = COCKPIT_INTERNAL_METRICS (channel); JsonObject *options; JsonArray *metrics; int i; COCKPIT_CHANNEL_CLASS (cockpit_internal_metrics_parent_class)->prepare (channel); options = cockpit_channel_get_options (channel); /* "instances" option */ if (!cockpit_json_get_strv (options, "instances", NULL, (gchar ***)&self->instances)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"instances\" option (not an array of strings)"); return; } /* "omit-instances" option */ if (!cockpit_json_get_strv (options, "omit-instances", NULL, (gchar ***)&self->omit_instances)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"omit-instances\" option (not an array of strings)"); return; } /* "metrics" option */ self->n_metrics = 0; if (!cockpit_json_get_array (options, "metrics", NULL, &metrics)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"metrics\" option was specified (not an array)"); return; } if (metrics) self->n_metrics = json_array_get_length (metrics); self->metrics = g_new0 (MetricInfo, self->n_metrics); for (i = 0; i < self->n_metrics; i++) { MetricInfo *info = &self->metrics[i]; if (!convert_metric_description (self, json_array_get_element (metrics, i), info, i)) return; if (!info->desc) { cockpit_channel_close (channel, "not-supported"); return; } } /* "interval" option */ if (!cockpit_json_get_int (options, "interval", 1000, &self->interval)) { cockpit_channel_fail (channel, "protocol-error", "invalid \"interval\" option"); return; } else if (self->interval <= 0 || self->interval > G_MAXINT) { cockpit_channel_fail (channel, "protocol-error", "invalid \"interval\" value: %" G_GINT64_FORMAT, self->interval); return; } self->need_meta = TRUE; cockpit_metrics_metronome (COCKPIT_METRICS (self), self->interval); cockpit_channel_ready (channel, NULL); }
static GSocketConnectable * parse_address (CockpitChannel *self, gchar **possible_name, gboolean *local_address) { GSocketConnectable *connectable = NULL; const gchar *unix_path; const gchar *internal; const gchar *address; JsonObject *options; gboolean local = FALSE; GError *error = NULL; const gchar *host; gint64 port; gchar *name = NULL; gboolean open = FALSE; options = self->priv->open_options; if (!cockpit_json_get_string (options, "unix", NULL, &unix_path)) { cockpit_channel_fail (self, "protocol-error", "invalid \"unix\" option in channel"); goto out; } if (!cockpit_json_get_int (options, "port", G_MAXINT64, &port)) { cockpit_channel_fail (self, "protocol-error", "invalid \"port\" option in channel"); goto out; } if (!cockpit_json_get_string (options, "internal", NULL, &internal)) { cockpit_channel_fail (self, "protocol-error", "invalid \"internal\" option in channel"); goto out; } if (!cockpit_json_get_string (options, "address", NULL, &address)) { cockpit_channel_fail (self, "protocol-error", "invalid \"address\" option in channel"); goto out; } if (port != G_MAXINT64 && unix_path) { cockpit_channel_fail (self, "protocol-error", "cannot specify both \"port\" and \"unix\" options"); goto out; } else if (port != G_MAXINT64) { if (port <= 0 || port > 65535) { cockpit_channel_fail (self, "protocol-error", "received invalid \"port\" option"); goto out; } if (address) { connectable = g_network_address_new (address, port); host = address; /* This isn't perfect, but matches the use case. Specify address => non-local */ local = FALSE; } else if (cockpit_bridge_local_address) { connectable = g_network_address_parse (cockpit_bridge_local_address, port, &error); host = cockpit_bridge_local_address; local = TRUE; } else { connectable = cockpit_loopback_new (port); host = "localhost"; local = TRUE; } if (error != NULL) { cockpit_channel_fail (self, "internal-error", "couldn't parse local address: %s: %s", host, error->message); goto out; } else { name = g_strdup_printf ("%s:%d", host, (gint)port); } } else if (unix_path) { name = g_strdup (unix_path); connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new (unix_path)); local = FALSE; } else if (internal) { gboolean reg = lookup_internal (internal, &connectable); if (!connectable) { if (reg) cockpit_channel_close (self, "not-found"); else cockpit_channel_fail (self, "not-found", "couldn't find internal address: %s", internal); goto out; } name = g_strdup (internal); connectable = g_object_ref (connectable); local = FALSE; } else { cockpit_channel_fail (self, "protocol-error", "no \"port\" or \"unix\" or other address option for channel"); goto out; } open = TRUE; out: g_clear_error (&error); if (open) { if (possible_name) *possible_name = g_strdup (name); if (local_address) *local_address = local; } else { if (connectable) g_object_unref (connectable); connectable = NULL; } g_free (name); return connectable; }