static void on_socket_close (WebSocketConnection *connection, CockpitChannelSocket *chock) { const gchar *problem = NULL; GBytes *payload; gushort code; code = web_socket_connection_get_close_code (chock->socket); if (code == WEB_SOCKET_CLOSE_NORMAL) { payload = cockpit_transport_build_control ("command", "done", "channel", chock->channel, NULL); cockpit_transport_send (chock->transport, NULL, payload); g_bytes_unref (payload); } else { problem = web_socket_connection_get_close_data (chock->socket); if (problem == NULL) problem = "disconnected"; } payload = cockpit_transport_build_control ("command", "close", "channel", chock->channel, "problem", problem, NULL); cockpit_transport_send (chock->transport, NULL, payload); g_bytes_unref (payload); cockpit_channel_socket_close (chock, problem); }
static CockpitChannelResponse * cockpit_channel_response_create (CockpitWebService *service, CockpitWebResponse *response, CockpitTransport *transport, const gchar *logname, GHashTable *headers, JsonObject *open) { CockpitChannelResponse *chesp; const gchar *payload; JsonObject *done; GBytes *bytes; payload = json_object_get_string_member (open, "payload"); chesp = g_new0 (CockpitChannelResponse, 1); chesp->response = g_object_ref (response); chesp->transport = g_object_ref (transport); chesp->headers = g_hash_table_ref (headers); chesp->channel = cockpit_web_service_unique_channel (service); chesp->open = json_object_ref (open); if (!cockpit_json_get_string (open, "path", chesp->channel, &chesp->logname)) chesp->logname = chesp->channel; json_object_set_string_member (open, "command", "open"); json_object_set_string_member (open, "channel", chesp->channel); /* Special handling for http-stream1, splice in headers, handle injection */ if (g_strcmp0 (payload, "http-stream1") == 0) chesp->transport_recv = g_signal_connect (transport, "recv", G_CALLBACK (on_httpstream_recv), chesp); else chesp->transport_recv = g_signal_connect (transport, "recv", G_CALLBACK (on_transport_recv), chesp); /* Special handling for http-stream2, splice in headers, handle injection */ if (g_strcmp0 (payload, "http-stream2") == 0) chesp->transport_control = g_signal_connect (transport, "control", G_CALLBACK (on_httpstream_control), chesp); else chesp->transport_control = g_signal_connect (transport, "control", G_CALLBACK (on_transport_control), chesp); chesp->transport_closed = g_signal_connect (transport, "closed", G_CALLBACK (on_transport_closed), chesp); bytes = cockpit_json_write_bytes (chesp->open); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); done = cockpit_transport_build_json ("command", "done", "channel", chesp->channel, NULL); bytes = cockpit_json_write_bytes (done); json_object_unref (done); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); return chesp; }
/** * cockpit_channel_control: * @self: the channel * @command: the control command * @options: optional control message or NULL * * Send a control message to the other side. * * If @options is not NULL, then it may be modified by this code. * * With @command of "done" will send an EOF to the other side. This * should only be called once. Whether an EOF should be sent or not * depends on the payload type. */ void cockpit_channel_control (CockpitChannel *self, const gchar *command, JsonObject *options) { JsonObject *object; GBytes *message; g_return_if_fail (COCKPIT_IS_CHANNEL (self)); g_return_if_fail (command != NULL); if (g_str_equal (command, "done")) { g_return_if_fail (self->priv->sent_done == FALSE); self->priv->sent_done = TRUE; } if (options) object = json_object_ref (options); else object = json_object_new (); json_object_set_string_member (object, "command", command); json_object_set_string_member (object, "channel", self->priv->id); message = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (self->priv->transport, NULL, message); g_bytes_unref (message); }
static void on_helper_read (CockpitPipe *pipe, GByteArray *buffer, gboolean eof, gpointer user_data) { ReauthorizeCaller *caller = user_data; JsonObject *object; GBytes *bytes; guint8 *lf; lf = memchr (buffer->data, '\n', buffer->len); if (!lf) return; /* Null terminate the challenge */ *lf = 0; g_debug ("got challenge from helper, will send to cockpit-ws: %s", (gchar *)buffer->data); /* send an authorize packet here */ object = json_object_new (); json_object_set_string_member (object, "command", "authorize"); json_object_set_string_member (object, "cookie", caller->cookie); json_object_set_string_member (object, "challenge", (gchar *)buffer->data); bytes = cockpit_json_write_bytes (object); json_object_unref (object); /* Consume from buffer, including null termination */ cockpit_pipe_skip (buffer, lf - buffer->data); cockpit_transport_send (caller->self->transport, NULL, bytes); g_bytes_unref (bytes); }
static void on_web_socket_message (WebSocketConnection *connection, WebSocketDataType type, GBytes *message, CockpitWebService *self) { CockpitSocket *socket; GBytes *payload; gchar *channel; socket = cockpit_socket_lookup_by_connection (&self->sockets, connection); g_return_if_fail (socket != NULL); payload = cockpit_transport_parse_frame (message, &channel); if (!payload) return; /* A control channel command */ if (!channel) { dispatch_inbound_command (self, socket, payload); } /* An actual payload message */ else if (!self->closing) { if (!self->sent_done) cockpit_transport_send (self->transport, channel, payload); } g_free (channel); g_bytes_unref (payload); }
static void send_init_command (CockpitTransport *transport) { const gchar *checksum; const gchar *name; JsonObject *object; GBytes *bytes; object = json_object_new (); json_object_set_string_member (object, "command", "init"); json_object_set_int_member (object, "version", 1); checksum = cockpit_packages_get_checksum (packages); if (checksum) json_object_set_string_member (object, "checksum", checksum); /* Happens when we're in --interact mode */ name = cockpit_dbus_internal_name (); if (name) json_object_set_string_member (object, "bridge-dbus-name", name); bytes = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); }
static void spawn_portal_bridge (CockpitPortal *self) { CockpitPipe *pipe; const gchar *data; const gchar **argv; g_assert (self->other == NULL); argv = current_argv (self); g_debug ("launching portal bridge: %s", argv[0]); pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE); self->other = cockpit_pipe_transport_new (pipe); g_object_unref (pipe); self->other_recv_sig = g_signal_connect (self->other, "recv", G_CALLBACK (on_other_recv), self); self->other_control_sig = g_signal_connect (self->other, "control", G_CALLBACK (on_other_control), self); self->other_closed_sig = g_signal_connect (self->other, "closed", G_CALLBACK (on_other_closed), self); if (!self->last_init) { data = "{\"command\":\"init\",\"version\":1}"; self->last_init = g_bytes_new_static (data, strlen (data)); } cockpit_transport_send (self->other, NULL, self->last_init); }
static gboolean on_other_control (CockpitTransport *transport, const char *command, const gchar *channel, JsonObject *options, GBytes *payload, gpointer user_data) { CockpitPortal *self = user_data; if (g_str_equal (command, "init")) { if (self->state == PORTAL_OPENING) transition_open (self); } /* Only forward close and done control messages back out */ else if (g_str_equal (command, "close") || g_str_equal (command, "done")) { if (self->channels && channel) g_hash_table_remove (self->channels, channel); g_debug ("portal channel closed: %s", channel); if (self->transport) cockpit_transport_send (self->transport, NULL, payload); } return TRUE; }
static void on_socket_message (WebSocketConnection *connection, WebSocketDataType type, GBytes *payload, CockpitChannelSocket *chock) { cockpit_transport_send (chock->transport, chock->channel, payload); }
/** * cockpit_channel_control: * @self: the channel * @command: the control command * @options: optional control message or NULL * * Send a control message to the other side. * * If @options is not NULL, then it may be modified by this code. * * With @command of "done" will send an EOF to the other side. This * should only be called once. Whether an EOF should be sent or not * depends on the payload type. */ void cockpit_channel_control (CockpitChannel *self, const gchar *command, JsonObject *options) { JsonObject *object; GBytes *message; const gchar *problem; gchar *problem_copy = NULL; g_return_if_fail (COCKPIT_IS_CHANNEL (self)); g_return_if_fail (command != NULL); if (g_str_equal (command, "done")) { g_return_if_fail (self->priv->sent_done == FALSE); self->priv->sent_done = TRUE; } /* If closing save the close options * and let close send the message */ else if (g_str_equal (command, "close")) { if (!self->priv->close_options) { /* Ref for close_options, freed in parent */ self->priv->close_options = json_object_ref (options); } if (!cockpit_json_get_string (options, "problem", NULL, &problem)) problem = NULL; /* Use a problem copy so it out lasts the value in close_options */ problem_copy = g_strdup (problem); cockpit_channel_close (self, problem_copy); goto out; } if (options) object = json_object_ref (options); else object = json_object_new (); json_object_set_string_member (object, "command", command); json_object_set_string_member (object, "channel", self->priv->id); message = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (self->priv->transport, NULL, message); g_bytes_unref (message); out: g_free (problem_copy); }
static gboolean process_kill (CockpitWebService *self, CockpitSocket *socket, JsonObject *options, GBytes *payload) { if (!self->sent_done) cockpit_transport_send (self->transport, NULL, payload); return TRUE; }
static void cockpit_channel_actual_send (CockpitChannel *self, GBytes *payload, gboolean trust_is_utf8) { GBytes *validated = NULL; guint64 out_sequence; JsonObject *ping; gsize size; g_return_if_fail (self->priv->out_buffer == NULL); g_return_if_fail (self->priv->buffer_timeout == 0); if (!trust_is_utf8) { if (!self->priv->binary_ok) payload = validated = cockpit_unicode_force_utf8 (payload); } cockpit_transport_send (self->priv->transport, self->priv->id, payload); /* A wraparound of our gint64 size? */ if (self->priv->flow_control) { size = g_bytes_get_size (payload); g_return_if_fail (G_MAXINT64 - size > self->priv->out_sequence); /* How many bytes have been sent (queued) */ out_sequence = self->priv->out_sequence + size; /* Every CHANNEL_FLOW_PING bytes we send a ping */ if (out_sequence / CHANNEL_FLOW_PING != self->priv->out_sequence / CHANNEL_FLOW_PING) { ping = json_object_new (); json_object_set_int_member (ping, "sequence", out_sequence); cockpit_channel_control (self, "ping", ping); g_debug ("%s: sending ping with sequence: %" G_GINT64_FORMAT, self->priv->id, out_sequence); json_object_unref (ping); } /* If we've sent more than the window, apply back pressure */ self->priv->out_sequence = out_sequence; if (self->priv->out_sequence > self->priv->out_window) { g_debug ("%s: sent too much data without acknowledgement, emitting back pressure until %" G_GINT64_FORMAT, self->priv->id, self->priv->out_window); cockpit_flow_emit_pressure (COCKPIT_FLOW (self), TRUE); } } if (validated) g_bytes_unref (validated); }
static gboolean process_and_relay_close (CockpitWebService *self, CockpitSocket *socket, const gchar *channel, GBytes *payload) { gboolean valid; valid = process_close (self, socket, channel); if (valid && !self->sent_done) cockpit_transport_send (self->transport, NULL, payload); return valid; }
static void send_init_command (CockpitTransport *transport) { JsonObject *object; GBytes *bytes; object = json_object_new (); json_object_set_string_member (object, "command", "init"); json_object_set_int_member (object, "version", 1); bytes = cockpit_json_write_bytes (object); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); }
static void on_socket_open (WebSocketConnection *connection, CockpitChannelSocket *chock) { GBytes *payload; /* * Actually open the channel. We wait until the WebSocket is open * before doing this, so we don't receive messages from the bridge * before the websocket is open. */ payload = cockpit_json_write_bytes (chock->open); cockpit_transport_send (chock->transport, NULL, payload); g_bytes_unref (payload); }
static gboolean on_other_recv (CockpitTransport *transport, const gchar *channel, GBytes *payload, gpointer user_data) { CockpitPortal *self = user_data; if (channel) { if (self->transport) cockpit_transport_send (self->transport, channel, payload); return TRUE; } return FALSE; }
/** * cockpit_channel_send: * @self: a pipe * @payload: the message payload to send * @trust_is_utf8: set to true if sure data is UTF8 * * Called by implementations to send a message over the transport * on the right channel. * * This message is queued, and sent once the transport can. */ void cockpit_channel_send (CockpitChannel *self, GBytes *payload, gboolean trust_is_utf8) { GBytes *validated = NULL; if (!trust_is_utf8) { if (!self->priv->binary_ok) payload = validated = cockpit_unicode_force_utf8 (payload); } cockpit_transport_send (self->priv->transport, self->priv->id, payload); if (validated) g_bytes_unref (validated); }
static gboolean send_to_portal (CockpitPortal *self, const gchar *channel, GBytes *payload, CockpitPortalFlags flags) { CockpitPortalMessage *message; /* What we do here depends on the state */ switch (self->state) { case PORTAL_NONE: transition_opening (self); /* fall through */ case PORTAL_OPENING: if (!self->queue) self->queue = g_queue_new (); message = cockpit_portal_message_new (intern_string (self, channel), payload, flags); g_queue_push_tail (self->queue, message); return TRUE; case PORTAL_OPEN: if (self->transport) cockpit_transport_send (self->other, channel, payload); return TRUE; case PORTAL_FAILED: if ((flags & COCKPIT_PORTAL_FALLBACK) == 0) { if (channel && g_hash_table_contains (self->channels, channel)) { g_hash_table_remove (self->channels, channel); send_close_channel (self, channel, self->problem); } return TRUE; } return FALSE; default: g_assert_not_reached (); } }
static gboolean process_and_relay_open (CockpitWebService *self, CockpitSocket *socket, const gchar *channel, JsonObject *options) { WebSocketDataType data_type = WEB_SOCKET_DATA_TEXT; GBytes *payload; if (self->closing) { g_debug ("Ignoring open command while web socket is closing"); return TRUE; } if (channel == NULL) { g_warning ("open command is missing the 'channel' field"); return FALSE; } if (cockpit_socket_lookup_by_channel (&self->sockets, channel)) { g_warning ("cannot open a channel %s with the same id as another channel", channel); return FALSE; } if (!cockpit_web_service_parse_binary (options, &data_type)) return FALSE; if (socket) cockpit_socket_add_channel (&self->sockets, socket, channel, data_type); if (!self->sent_done) { payload = cockpit_json_write_bytes (options); cockpit_transport_send (self->transport, NULL, payload); g_bytes_unref (payload); } return TRUE; }
static const gchar * process_transport_init (CockpitWebService *self, CockpitTransport *transport, JsonObject *options) { JsonObject *object; GBytes *payload; gint64 version; if (!cockpit_json_get_int (options, "version", -1, &version)) { g_warning ("invalid version field in init message"); return "protocol-error"; } if (version == 1) { g_debug ("received init message"); self->init_received = TRUE; g_object_set_data_full (G_OBJECT (transport), "init", json_object_ref (options), (GDestroyNotify) json_object_unref); /* Always send an init message down the new transport */ object = cockpit_transport_build_json ("command", "init", NULL); json_object_set_int_member (object, "version", 1); json_object_set_string_member (object, "host", "localhost"); payload = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (transport, NULL, payload); g_bytes_unref (payload); } else { g_message ("unsupported version of cockpit protocol: %" G_GINT64_FORMAT, version); return "not-supported"; } g_signal_emit (self, sig_transport_init, 0); return NULL; }
static gboolean on_web_socket_closing (WebSocketConnection *connection, CockpitWebService *self) { CockpitSocket *socket; GHashTable *snapshot; GHashTableIter iter; const gchar *channel; GBytes *payload; g_debug ("web socket closing"); if (self->sent_done) return TRUE; /* Close any channels that were opened by this web socket */ snapshot = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); socket = cockpit_socket_lookup_by_connection (&self->sockets, connection); if (socket) { g_hash_table_iter_init (&iter, socket->channels); while (g_hash_table_iter_next (&iter, (gpointer *)&channel, NULL)) { g_hash_table_add (snapshot, g_strdup (channel)); } } g_hash_table_iter_init (&iter, snapshot); while (g_hash_table_iter_next (&iter, (gpointer *)&channel, NULL)) { payload = cockpit_transport_build_control ("command", "close", "channel", channel, "problem", "disconnected", NULL); cockpit_transport_send (self->transport, NULL, payload); g_bytes_unref (payload); } g_hash_table_destroy (snapshot); return TRUE; }
static void send_init_command (CockpitTransport *transport) { const gchar *checksum; JsonObject *object; GBytes *bytes; object = json_object_new (); json_object_set_string_member (object, "command", "init"); json_object_set_int_member (object, "version", 1); checksum = cockpit_packages_get_checksum (packages); if (checksum) json_object_set_string_member (object, "checksum", checksum); bytes = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); }
/** * cockpit_channel_done: * @self: the channel * * Send an EOF to the other side. This should only be called once. * Whether an EOF should be sent or not depends on the payload type. */ void cockpit_channel_done (CockpitChannel *self) { JsonObject *object; GBytes *message; g_return_if_fail (COCKPIT_IS_CHANNEL (self)); g_return_if_fail (self->priv->sent_done == FALSE); self->priv->sent_done = TRUE; object = json_object_new (); json_object_set_string_member (object, "command", "done"); json_object_set_string_member (object, "channel", self->priv->id); message = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (self->priv->transport, NULL, message); g_bytes_unref (message); }
static void cockpit_channel_real_close (CockpitChannel *self, const gchar *problem) { JsonObject *object; GBytes *message; if (self->priv->sent_close) return; self->priv->sent_close = TRUE; if (!self->priv->transport_closed) { flush_buffer (self); if (self->priv->close_options) { object = self->priv->close_options; self->priv->close_options = NULL; } else { object = json_object_new (); } json_object_set_string_member (object, "command", "close"); json_object_set_string_member (object, "channel", self->priv->id); if (problem) json_object_set_string_member (object, "problem", problem); message = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (self->priv->transport, NULL, message); g_bytes_unref (message); } g_signal_emit (self, cockpit_channel_sig_closed, 0, problem); }
static gboolean process_ping (CockpitChannel *self, JsonObject *ping) { GBytes *payload; if (self->priv->throttled) { g_debug ("%s: received ping while throttled", self->priv->id); g_queue_push_tail (self->priv->throttled, json_object_ref (ping)); return FALSE; } else { g_debug ("%s: replying to ping with pong", self->priv->id); json_object_set_string_member (ping, "command", "pong"); payload = cockpit_json_write_bytes (ping); cockpit_transport_send (self->priv->transport, NULL, payload); g_bytes_unref (payload); return TRUE; } }
static void send_close_channel (CockpitPortal *self, const gchar *channel_id, const gchar *problem) { JsonObject *object; GBytes *bytes; g_debug ("sending close for portal channel: %s: %s", channel_id, problem); object = json_object_new (); json_object_set_string_member (object, "command", "close"); json_object_set_string_member (object, "channel", channel_id); json_object_set_string_member (object, "problem", problem); bytes = cockpit_json_write_bytes (object); json_object_unref (object); if (self->transport) cockpit_transport_send (self->transport, NULL, bytes); g_bytes_unref (bytes); }
static void cockpit_channel_real_close (CockpitChannel *self, const gchar *problem) { const gchar *reason = problem; JsonObject *object; GBytes *message; if (self->priv->closed) return; self->priv->closed = TRUE; if (reason == NULL) reason = ""; if (self->priv->close_options) { object = self->priv->close_options; self->priv->close_options = NULL; } else { object = json_object_new (); } json_object_set_string_member (object, "command", "close"); json_object_set_string_member (object, "channel", self->priv->id); json_object_set_string_member (object, "reason", reason); message = cockpit_json_write_bytes (object); json_object_unref (object); cockpit_transport_send (self->priv->transport, 0, message); g_bytes_unref (message); g_signal_emit (self, cockpit_channel_sig_closed, 0, problem); }
/** * cockpit_channel_send: * @self: a pipe * @payload: the message payload to send * @trust_is_utf8: set to true if sure data is UTF8 * * Called by implementations to send a message over the transport * on the right channel. * * This message is queued, and sent once the transport can. */ void cockpit_channel_send (CockpitChannel *self, GBytes *payload, gboolean trust_is_utf8) { GBytes *encoded = NULL; GBytes *validated = NULL; if (!trust_is_utf8) { if (!self->priv->binary_ok) payload = validated = check_utf8_and_force_if_necessary (payload); } if (self->priv->base64_encoding) payload = encoded = base64_encode (payload); cockpit_transport_send (self->priv->transport, self->priv->id, payload); if (encoded) g_bytes_unref (encoded); if (validated) g_bytes_unref (validated); }
static void send_init_command (CockpitTransport *transport, gboolean interactive) { const gchar *checksum; JsonObject *object; GBytes *bytes; object = json_object_new (); json_object_set_string_member (object, "command", "init"); json_object_set_int_member (object, "version", 1); /* * When in interactive mode pretend we received an init * message, and don't print one out. */ if (interactive) { json_object_set_string_member (object, "host", "localhost"); } else { checksum = cockpit_packages_get_checksum (packages); if (checksum) json_object_set_string_member (object, "checksum", checksum); } bytes = cockpit_json_write_bytes (object); json_object_unref (object); if (interactive) cockpit_transport_emit_recv (transport, NULL, bytes); else cockpit_transport_send (transport, NULL, bytes); g_bytes_unref (bytes); }
/** * cockpit_channel_send: * @self: a pipe * @payload: the message payload to send * * Called by implementations to send a message over the transport * on the right channel. * * This message is queued, and sent once the transport can. */ void cockpit_channel_send (CockpitChannel *self, GBytes *payload) { cockpit_transport_send (self->priv->transport, self->priv->id, payload); }