/*
* This file is part of Cockpit.
*
* Copyright (C) 2015 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 "cockpitchannelsocket.h"
#include "websocket/websocket.h"
#include
typedef struct {
gchar *channel;
/* The WebSocket side of things */
WebSocketConnection *socket;
WebSocketDataType data_type;
gulong socket_open;
gulong socket_message;
gulong socket_close;
/* The bridge side of things */
CockpitTransport *transport;
JsonObject *open;
gulong transport_recv;
gulong transport_control;
gulong transport_closed;
} CockpitChannelSocket;
static void cockpit_channel_socket_close (CockpitChannelSocket *socket,
const gchar *problem);
static gboolean
on_transport_recv (CockpitTransport *transport,
const gchar *channel,
GBytes *payload,
CockpitChannelSocket *chock)
{
if (channel && g_str_equal (channel, chock->channel))
{
if (web_socket_connection_get_ready_state (chock->socket) == WEB_SOCKET_STATE_OPEN)
web_socket_connection_send (chock->socket, chock->data_type, NULL, payload);
return TRUE;
}
return FALSE;
}
static gboolean
on_transport_control (CockpitTransport *transport,
const char *command,
const gchar *channel,
JsonObject *options,
GBytes *payload,
CockpitChannelSocket *chock)
{
const gchar *problem;
if (channel && g_str_equal (channel, chock->channel))
{
if (g_str_equal (command, "close"))
{
if (!cockpit_json_get_string (options, "problem", NULL, &problem))
problem = NULL;
cockpit_channel_socket_close (chock, problem);
}
/* Any other control message for this channel is discarded */
return TRUE;
}
return FALSE;
}
static void
on_transport_closed (CockpitTransport *transport,
const gchar *problem,
CockpitChannelSocket *chock)
{
cockpit_channel_socket_close (chock, problem);
}
static void
cockpit_channel_socket_close (CockpitChannelSocket *chock,
const gchar *problem)
{
gushort code;
g_free (chock->channel);
g_signal_handler_disconnect (chock->transport, chock->transport_recv);
g_signal_handler_disconnect (chock->transport, chock->transport_control);
g_signal_handler_disconnect (chock->transport, chock->transport_closed);
json_object_unref (chock->open);
g_object_unref (chock->transport);
g_signal_handler_disconnect (chock->socket, chock->socket_open);
g_signal_handler_disconnect (chock->socket, chock->socket_message);
g_signal_handler_disconnect (chock->socket, chock->socket_close);
if (web_socket_connection_get_ready_state (chock->socket) < WEB_SOCKET_STATE_CLOSING)
{
if (problem)
code = WEB_SOCKET_CLOSE_GOING_AWAY;
else
code = WEB_SOCKET_CLOSE_NORMAL;
web_socket_connection_close (chock->socket, code, problem);
}
g_object_unref (chock->socket);
g_free (chock);
}
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 void
on_socket_message (WebSocketConnection *connection,
WebSocketDataType type,
GBytes *payload,
CockpitChannelSocket *chock)
{
cockpit_transport_send (chock->transport, chock->channel, payload);
}
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 void
respond_with_error (const gchar *original_path,
const gchar *path,
GIOStream *io_stream,
GHashTable *headers,
guint status,
const gchar *message)
{
CockpitWebResponse *response;
response = cockpit_web_response_new (io_stream, original_path, path, NULL, headers);
cockpit_web_response_error (response, status, NULL, "%s", message);
g_object_unref (response);
}
void
cockpit_channel_socket_open (CockpitWebService *service,
JsonObject *open,
const gchar *original_path,
const gchar *path,
GIOStream *io_stream,
GHashTable *headers,
GByteArray *input_buffer)
{
CockpitChannelSocket *chock = NULL;
WebSocketDataType data_type;
CockpitTransport *transport;
gchar **protocols = NULL;
if (!cockpit_web_service_parse_external (open, NULL, NULL, NULL, &protocols) ||
!cockpit_web_service_parse_binary (open, &data_type))
{
respond_with_error (original_path, path, io_stream, headers, 400, "Bad channel request");
goto out;
}
transport = cockpit_web_service_get_transport (service);
if (!transport)
{
respond_with_error (original_path, path, io_stream, headers, 502, "Failed to open channel transport");
goto out;
}
chock = g_new0 (CockpitChannelSocket, 1);
chock->channel = cockpit_web_service_unique_channel (service);
chock->open = json_object_ref (open);
chock->data_type = data_type;
json_object_set_string_member (open, "command", "open");
json_object_set_string_member (open, "channel", chock->channel);
chock->socket = cockpit_web_service_create_socket ((const gchar **)protocols, original_path,
io_stream, headers, input_buffer);
chock->socket_open = g_signal_connect (chock->socket, "open", G_CALLBACK (on_socket_open), chock);
chock->socket_message = g_signal_connect (chock->socket, "message", G_CALLBACK (on_socket_message), chock);
chock->socket_close = g_signal_connect (chock->socket, "close", G_CALLBACK (on_socket_close), chock);
chock->transport = g_object_ref (transport);
chock->transport_recv = g_signal_connect (chock->transport, "recv", G_CALLBACK (on_transport_recv), chock);
chock->transport_control = g_signal_connect (chock->transport, "control", G_CALLBACK (on_transport_control), chock);
chock->transport_closed = g_signal_connect (chock->transport, "closed", G_CALLBACK (on_transport_closed), chock);
out:
g_free (protocols);
}