1478 lines
41 KiB
C
1478 lines
41 KiB
C
/*
|
|
* This file is part of Cockpit.
|
|
*
|
|
* Copyright (C) 2013-2014 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "cockpitwebservice.h"
|
|
|
|
#include "cockpitcompat.h"
|
|
#include "cockpitws.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <json-glib/json-glib.h>
|
|
#include <gio/gunixinputstream.h>
|
|
#include <gio/gunixoutputstream.h>
|
|
|
|
#include "common/cockpitauthorize.h"
|
|
#include "common/cockpitconf.h"
|
|
#include "common/cockpithex.h"
|
|
#include "common/cockpitjson.h"
|
|
#include "common/cockpitlog.h"
|
|
#include "common/cockpitmemory.h"
|
|
#include "common/cockpitsystem.h"
|
|
#include "common/cockpitwebresponse.h"
|
|
#include "common/cockpitwebserver.h"
|
|
|
|
#include "websocket/websocket.h"
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
const gchar *cockpit_ws_default_host_header =
|
|
"0.0.0.0:0"; /* Must be something invalid */
|
|
|
|
const gchar *cockpit_ws_default_protocol_header = NULL;
|
|
|
|
guint cockpit_ws_ping_interval = 5;
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Web Socket Info
|
|
*/
|
|
|
|
typedef struct {
|
|
gchar *id;
|
|
WebSocketConnection *connection;
|
|
GHashTable *channels;
|
|
JsonObject *init_received;
|
|
} CockpitSocket;
|
|
|
|
typedef struct {
|
|
GHashTable *by_channel;
|
|
GHashTable *by_connection;
|
|
guint next_socket_id;
|
|
} CockpitSockets;
|
|
|
|
static void
|
|
cockpit_socket_free (gpointer data)
|
|
{
|
|
CockpitSocket *socket = data;
|
|
g_hash_table_unref (socket->channels);
|
|
if (socket->init_received)
|
|
json_object_unref (socket->init_received);
|
|
g_object_unref (socket->connection);
|
|
g_free (socket->id);
|
|
g_free (socket);
|
|
}
|
|
|
|
static void
|
|
cockpit_sockets_init (CockpitSockets *sockets)
|
|
{
|
|
sockets->next_socket_id = 1;
|
|
|
|
sockets->by_channel = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
/* This owns the socket */
|
|
sockets->by_connection = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
NULL, cockpit_socket_free);
|
|
}
|
|
|
|
inline static CockpitSocket *
|
|
cockpit_socket_lookup_by_connection (CockpitSockets *sockets,
|
|
WebSocketConnection *connection)
|
|
{
|
|
return g_hash_table_lookup (sockets->by_connection, connection);
|
|
}
|
|
|
|
inline static CockpitSocket *
|
|
cockpit_socket_lookup_by_channel (CockpitSockets *sockets,
|
|
const gchar *channel)
|
|
{
|
|
return g_hash_table_lookup (sockets->by_channel, channel);
|
|
}
|
|
|
|
static void
|
|
cockpit_socket_remove_channel (CockpitSockets *sockets,
|
|
CockpitSocket *socket,
|
|
const gchar *channel)
|
|
{
|
|
g_debug ("%s remove channel %s for socket", socket->id, channel);
|
|
g_hash_table_remove (sockets->by_channel, channel);
|
|
g_hash_table_remove (socket->channels, channel);
|
|
}
|
|
|
|
static void
|
|
cockpit_socket_add_channel (CockpitSockets *sockets,
|
|
CockpitSocket *socket,
|
|
const gchar *channel,
|
|
WebSocketDataType data_type)
|
|
{
|
|
gchar *chan;
|
|
|
|
chan = g_strdup (channel);
|
|
g_hash_table_insert (sockets->by_channel, chan, socket);
|
|
g_hash_table_replace (socket->channels, chan, GINT_TO_POINTER (data_type));
|
|
|
|
g_debug ("%s added channel %s to socket", socket->id, channel);
|
|
}
|
|
|
|
static CockpitSocket *
|
|
cockpit_socket_track (CockpitSockets *sockets,
|
|
WebSocketConnection *connection)
|
|
{
|
|
CockpitSocket *socket;
|
|
|
|
socket = g_new0 (CockpitSocket, 1);
|
|
socket->id = g_strdup_printf ("%u:", sockets->next_socket_id++);
|
|
socket->connection = g_object_ref (connection);
|
|
socket->channels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
g_debug ("%s new socket", socket->id);
|
|
|
|
/* This owns the socket */
|
|
g_hash_table_insert (sockets->by_connection, connection, socket);
|
|
|
|
return socket;
|
|
}
|
|
|
|
static void
|
|
cockpit_socket_destroy (CockpitSockets *sockets,
|
|
CockpitSocket *socket)
|
|
{
|
|
GHashTableIter iter;
|
|
const gchar *chan;
|
|
|
|
g_debug ("%s destroy socket", socket->id);
|
|
|
|
g_hash_table_iter_init (&iter, socket->channels);
|
|
while (g_hash_table_iter_next (&iter, (gpointer *)&chan, NULL))
|
|
g_hash_table_remove (sockets->by_channel, chan);
|
|
g_hash_table_remove_all (socket->channels);
|
|
|
|
/* This owns the socket */
|
|
g_hash_table_remove (sockets->by_connection, socket->connection);
|
|
}
|
|
|
|
static void
|
|
cockpit_sockets_close (CockpitSockets *sockets,
|
|
const gchar *problem)
|
|
{
|
|
GHashTableIter iter;
|
|
CockpitSocket *socket;
|
|
|
|
if (!problem)
|
|
problem = "terminated";
|
|
|
|
g_hash_table_iter_init (&iter, sockets->by_connection);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&socket))
|
|
{
|
|
if (web_socket_connection_get_ready_state (socket->connection) < WEB_SOCKET_STATE_CLOSING)
|
|
web_socket_connection_close (socket->connection, WEB_SOCKET_CLOSE_GOING_AWAY, problem);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cockpit_sockets_cleanup (CockpitSockets *sockets)
|
|
{
|
|
g_hash_table_destroy (sockets->by_connection);
|
|
g_hash_table_destroy (sockets->by_channel);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Web Socket Routing
|
|
*/
|
|
|
|
struct _CockpitWebService {
|
|
GObject parent;
|
|
|
|
CockpitCreds *creds;
|
|
CockpitSockets sockets;
|
|
gboolean closing;
|
|
GBytes *control_prefix;
|
|
guint ping_timeout;
|
|
gint callers;
|
|
guint next_internal_id;
|
|
|
|
gint credential_requests;
|
|
|
|
CockpitTransport *transport;
|
|
JsonObject *init_received;
|
|
gulong control_sig;
|
|
gulong recv_sig;
|
|
gulong closed_sig;
|
|
gboolean sent_done;
|
|
|
|
GHashTable *checksum_by_host;
|
|
GHashTable *host_by_checksum;
|
|
};
|
|
|
|
typedef struct {
|
|
GObjectClass parent;
|
|
} CockpitWebServiceClass;
|
|
|
|
static guint sig_idling = 0;
|
|
static guint sig_destroy = 0;
|
|
|
|
G_DEFINE_TYPE (CockpitWebService, cockpit_web_service, G_TYPE_OBJECT);
|
|
|
|
static void
|
|
cockpit_web_service_dispose (GObject *object)
|
|
{
|
|
CockpitWebService *self = COCKPIT_WEB_SERVICE (object);
|
|
gboolean emit = FALSE;
|
|
|
|
if (self->control_sig)
|
|
g_signal_handler_disconnect (self->transport, self->control_sig);
|
|
self->control_sig = 0;
|
|
|
|
if (self->recv_sig)
|
|
g_signal_handler_disconnect (self->transport, self->recv_sig);
|
|
self->recv_sig = 0;
|
|
|
|
if (self->closed_sig)
|
|
g_signal_handler_disconnect (self->transport, self->closed_sig);
|
|
self->closed_sig = 0;
|
|
|
|
if (!self->sent_done)
|
|
{
|
|
self->sent_done = TRUE;
|
|
cockpit_transport_close (self->transport, NULL);
|
|
}
|
|
|
|
if (!self->closing)
|
|
{
|
|
g_debug ("web service closing");
|
|
emit = TRUE;
|
|
}
|
|
self->closing = TRUE;
|
|
|
|
cockpit_sockets_close (&self->sockets, NULL);
|
|
|
|
if (emit)
|
|
g_signal_emit (self, sig_destroy, 0);
|
|
|
|
G_OBJECT_CLASS (cockpit_web_service_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
cockpit_web_service_finalize (GObject *object)
|
|
{
|
|
CockpitWebService *self = COCKPIT_WEB_SERVICE (object);
|
|
|
|
cockpit_sockets_cleanup (&self->sockets);
|
|
|
|
if (self->transport)
|
|
g_object_unref (self->transport);
|
|
if (self->init_received)
|
|
json_object_unref (self->init_received);
|
|
|
|
g_bytes_unref (self->control_prefix);
|
|
cockpit_creds_unref (self->creds);
|
|
if (self->ping_timeout)
|
|
g_source_remove (self->ping_timeout);
|
|
|
|
g_hash_table_destroy (self->host_by_checksum);
|
|
g_hash_table_destroy (self->checksum_by_host);
|
|
|
|
G_OBJECT_CLASS (cockpit_web_service_parent_class)->finalize (object);
|
|
}
|
|
|
|
gchar *
|
|
cockpit_web_service_unique_channel (CockpitWebService *self)
|
|
{
|
|
return g_strdup_printf ("0:%d", self->next_internal_id++);
|
|
}
|
|
|
|
static void
|
|
caller_begin (CockpitWebService *self)
|
|
{
|
|
g_object_ref (self);
|
|
self->callers++;
|
|
}
|
|
|
|
static void
|
|
caller_end (CockpitWebService *self)
|
|
{
|
|
g_return_if_fail (self->callers > 0);
|
|
self->callers--;
|
|
if (self->callers == 0)
|
|
g_signal_emit (self, sig_idling, 0);
|
|
g_object_unref (self);
|
|
}
|
|
|
|
static void
|
|
outbound_protocol_error (CockpitWebService *self,
|
|
CockpitTransport *transport,
|
|
const gchar *problem)
|
|
{
|
|
if (problem == NULL)
|
|
problem = "protocol-error";
|
|
cockpit_transport_close (transport, problem);
|
|
}
|
|
|
|
static gboolean
|
|
process_close (CockpitWebService *self,
|
|
CockpitSocket *socket,
|
|
const gchar *channel)
|
|
{
|
|
if (socket)
|
|
cockpit_socket_remove_channel (&self->sockets, socket, channel);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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 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 gboolean
|
|
process_ping (CockpitWebService *self,
|
|
CockpitSocket *socket,
|
|
JsonObject *options)
|
|
{
|
|
GBytes *payload;
|
|
|
|
/* Respond to a ping without a channel, by saying "pong" */
|
|
json_object_set_string_member (options, "command", "pong");
|
|
payload = cockpit_json_write_bytes (options);
|
|
if (web_socket_connection_get_ready_state (socket->connection) == WEB_SOCKET_STATE_OPEN)
|
|
web_socket_connection_send (socket->connection, WEB_SOCKET_DATA_TEXT, self->control_prefix, payload);
|
|
g_bytes_unref (payload);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
send_socket_hints (CockpitWebService *self,
|
|
const gchar *name,
|
|
const gchar *value)
|
|
{
|
|
CockpitSocket *socket;
|
|
GHashTableIter iter;
|
|
GBytes *payload;
|
|
|
|
payload = cockpit_transport_build_control ("command", "hint", name, value, NULL);
|
|
g_hash_table_iter_init (&iter, self->sockets.by_connection);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&socket))
|
|
{
|
|
if (web_socket_connection_get_ready_state (socket->connection) == WEB_SOCKET_STATE_OPEN)
|
|
{
|
|
web_socket_connection_send (socket->connection, WEB_SOCKET_DATA_TEXT,
|
|
self->control_prefix, payload);
|
|
}
|
|
}
|
|
g_bytes_unref (payload);
|
|
}
|
|
|
|
static void
|
|
clear_and_free_string (gpointer data)
|
|
{
|
|
cockpit_memory_clear (data, -1);
|
|
free (data);
|
|
}
|
|
|
|
static gboolean
|
|
process_socket_authorize (CockpitWebService *self,
|
|
CockpitSocket *socket,
|
|
const gchar *channel,
|
|
JsonObject *options,
|
|
GBytes *payload)
|
|
{
|
|
const gchar *response = NULL;
|
|
gboolean ret = FALSE;
|
|
GBytes *bytes = NULL;
|
|
char *password = NULL;
|
|
char *user = NULL;
|
|
char *type = NULL;
|
|
gpointer data;
|
|
gsize length;
|
|
|
|
if (!cockpit_json_get_string (options, "response", NULL, &response))
|
|
{
|
|
g_warning ("%s: received invalid \"response\" field in authorize command", socket->id);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
if (response)
|
|
{
|
|
if (!cockpit_authorize_type (response, &type) || !g_str_equal (type, "basic"))
|
|
goto out;
|
|
|
|
password = cockpit_authorize_parse_basic (response, &user);
|
|
if (password && !user)
|
|
{
|
|
cockpit_memory_clear (password, -1);
|
|
free (password);
|
|
password = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
send_socket_hints (self, "credential",
|
|
cockpit_creds_get_password (self->creds) ? "password" : "none");
|
|
if (self->credential_requests)
|
|
send_socket_hints (self, "credential", "request");
|
|
goto out;
|
|
}
|
|
|
|
if (password == NULL)
|
|
{
|
|
send_socket_hints (self, "credential", "none");
|
|
self->credential_requests = 0;
|
|
bytes = NULL;
|
|
}
|
|
else
|
|
{
|
|
send_socket_hints (self, "credential", "password");
|
|
bytes = g_bytes_new_with_free_func (password, strlen (password),
|
|
clear_and_free_string, password);
|
|
password = NULL;
|
|
}
|
|
|
|
cockpit_creds_set_user (self->creds, user);
|
|
cockpit_creds_set_password (self->creds, bytes);
|
|
|
|
/* Clear out the payload memory */
|
|
data = (gpointer)g_bytes_get_data (payload, &length);
|
|
cockpit_memory_clear (data, length);
|
|
|
|
out:
|
|
free (type);
|
|
free (user);
|
|
if (bytes)
|
|
g_bytes_unref (bytes);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
authorize_check_user (CockpitCreds *creds,
|
|
const char *challenge)
|
|
{
|
|
char *subject = NULL;
|
|
gboolean ret = FALSE;
|
|
gchar *encoded = NULL;
|
|
const gchar *user;
|
|
|
|
if (!cockpit_authorize_subject (challenge, &subject))
|
|
goto out;
|
|
|
|
if (!subject || g_str_equal (subject, ""))
|
|
{
|
|
ret = TRUE;
|
|
}
|
|
else
|
|
{
|
|
user = cockpit_creds_get_user (creds);
|
|
if (user == NULL)
|
|
{
|
|
ret = TRUE;
|
|
}
|
|
else
|
|
{
|
|
encoded = cockpit_hex_encode (user, -1);
|
|
ret = g_str_equal (encoded, subject);
|
|
}
|
|
}
|
|
|
|
out:
|
|
g_free (encoded);
|
|
free (subject);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
process_transport_authorize (CockpitWebService *self,
|
|
CockpitTransport *transport,
|
|
JsonObject *options)
|
|
{
|
|
const gchar *cookie = NULL;
|
|
GBytes *payload;
|
|
char *type = NULL;
|
|
char *alloc = NULL;
|
|
const char *response = NULL;
|
|
const gchar *challenge;
|
|
const gchar *password;
|
|
const gchar *host;
|
|
GBytes *data;
|
|
|
|
if (!cockpit_json_get_string (options, "challenge", NULL, &challenge) ||
|
|
!cockpit_json_get_string (options, "cookie", NULL, &cookie) ||
|
|
!cockpit_json_get_string (options, "host", NULL, &host))
|
|
{
|
|
g_warning ("received invalid authorize command");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!challenge || !cookie)
|
|
{
|
|
g_message ("unsupported or unknown authorize command");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!cockpit_authorize_type (challenge, &type))
|
|
{
|
|
g_message ("received invalid authorize challenge command");
|
|
}
|
|
else if (g_str_equal (type, "plain1") ||
|
|
g_str_equal (type, "crypt1") ||
|
|
g_str_equal (type, "basic"))
|
|
{
|
|
data = cockpit_creds_get_password (self->creds);
|
|
if (!data)
|
|
{
|
|
g_debug ("%s: received \"authorize\" %s \"challenge\", but no password", host, type);
|
|
}
|
|
else if (!g_str_equal ("basic", type) && !authorize_check_user (self->creds, challenge))
|
|
{
|
|
g_debug ("received \"authorize\" %s \"challenge\", but for wrong user", type);
|
|
}
|
|
else
|
|
{
|
|
password = g_bytes_get_data (data, NULL);
|
|
if (g_str_equal (type, "crypt1"))
|
|
{
|
|
alloc = cockpit_compat_reply_crypt1 (challenge, password);
|
|
if (alloc)
|
|
response = alloc;
|
|
else
|
|
g_message ("failed to \"authorize\" crypt1 \"challenge\"");
|
|
}
|
|
else if (g_str_equal (type, "basic"))
|
|
{
|
|
response = cockpit_authorize_build_basic (cockpit_creds_get_user (self->creds),
|
|
password);
|
|
}
|
|
else
|
|
{
|
|
response = password;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Tell the frontend that we're reauthorizing */
|
|
if (self->init_received)
|
|
{
|
|
self->credential_requests++;
|
|
send_socket_hints (self, "credential", "request");
|
|
}
|
|
|
|
if (cookie && !self->sent_done)
|
|
{
|
|
payload = cockpit_transport_build_control ("command", "authorize",
|
|
"cookie", cookie,
|
|
"response", response ? response : "",
|
|
"host", host,
|
|
NULL);
|
|
cockpit_transport_send (transport, NULL, payload);
|
|
g_bytes_unref (payload);
|
|
}
|
|
|
|
free (type);
|
|
free (alloc);
|
|
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");
|
|
if (self->init_received)
|
|
json_object_unref (self->init_received);
|
|
self->init_received = json_object_ref (options);
|
|
|
|
/* 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";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
on_transport_control (CockpitTransport *transport,
|
|
const gchar *command,
|
|
const gchar *channel,
|
|
JsonObject *options,
|
|
GBytes *payload,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *problem = "protocol-error";
|
|
CockpitWebService *self = user_data;
|
|
CockpitSocket *socket = NULL;
|
|
gboolean valid = FALSE;
|
|
gboolean forward;
|
|
|
|
if (!channel)
|
|
{
|
|
if (g_strcmp0 (command, "init") == 0)
|
|
{
|
|
problem = process_transport_init (self, transport, options);
|
|
valid = (problem == NULL);
|
|
}
|
|
else if (!self->init_received)
|
|
{
|
|
g_message ("bridge did not send 'init' message first");
|
|
valid = FALSE;
|
|
}
|
|
else if (g_strcmp0 (command, "authorize") == 0)
|
|
{
|
|
valid = process_transport_authorize (self, transport, options);
|
|
}
|
|
else
|
|
{
|
|
g_debug ("received a %s unknown control command", command);
|
|
valid = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
socket = cockpit_socket_lookup_by_channel (&self->sockets, channel);
|
|
|
|
/* Usually all control messages with a channel are forwarded */
|
|
forward = TRUE;
|
|
|
|
if (g_strcmp0 (command, "close") == 0)
|
|
{
|
|
valid = process_close (self, socket, channel);
|
|
}
|
|
else
|
|
{
|
|
valid = TRUE;
|
|
}
|
|
|
|
if (forward)
|
|
{
|
|
/* Forward this message to the right websocket */
|
|
if (socket && web_socket_connection_get_ready_state (socket->connection) == WEB_SOCKET_STATE_OPEN)
|
|
{
|
|
web_socket_connection_send (socket->connection, WEB_SOCKET_DATA_TEXT,
|
|
self->control_prefix, payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!valid)
|
|
{
|
|
outbound_protocol_error (self, transport, problem);
|
|
}
|
|
|
|
return TRUE; /* handled */
|
|
}
|
|
|
|
static gboolean
|
|
on_transport_recv (CockpitTransport *transport,
|
|
const gchar *channel,
|
|
GBytes *payload,
|
|
gpointer user_data)
|
|
{
|
|
CockpitWebService *self = user_data;
|
|
WebSocketDataType data_type;
|
|
CockpitSocket *socket;
|
|
gchar *string;
|
|
GBytes *prefix;
|
|
|
|
if (!channel)
|
|
return FALSE;
|
|
|
|
/* Forward the message to the right socket */
|
|
socket = cockpit_socket_lookup_by_channel (&self->sockets, channel);
|
|
if (socket && web_socket_connection_get_ready_state (socket->connection) == WEB_SOCKET_STATE_OPEN)
|
|
{
|
|
string = g_strdup_printf ("%s\n", channel);
|
|
prefix = g_bytes_new_take (string, strlen (string));
|
|
data_type = GPOINTER_TO_INT (g_hash_table_lookup (socket->channels, channel));
|
|
web_socket_connection_send (socket->connection, data_type, prefix, payload);
|
|
g_bytes_unref (prefix);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
on_transport_closed (CockpitTransport *transport,
|
|
const gchar *problem,
|
|
gpointer user_data)
|
|
{
|
|
CockpitWebService *self = user_data;
|
|
|
|
/* Close all sockets */
|
|
cockpit_sockets_close (&self->sockets, problem);
|
|
|
|
/* Dispose web service */
|
|
g_object_run_dispose (G_OBJECT (self));
|
|
}
|
|
|
|
gboolean
|
|
cockpit_web_service_parse_binary (JsonObject *options,
|
|
WebSocketDataType *data_type)
|
|
{
|
|
const gchar *binary;
|
|
|
|
if (!cockpit_json_get_string (options, "binary", NULL, &binary))
|
|
{
|
|
g_warning ("invalid \"binary\" option");
|
|
return FALSE;
|
|
}
|
|
|
|
if (binary && g_str_equal (binary, "raw"))
|
|
*data_type = WEB_SOCKET_DATA_BINARY;
|
|
else
|
|
*data_type = WEB_SOCKET_DATA_TEXT;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
cockpit_web_service_parse_external (JsonObject *options,
|
|
const gchar **content_type,
|
|
const gchar **content_encoding,
|
|
const gchar **content_disposition,
|
|
gchar ***protocols)
|
|
{
|
|
JsonObject *external;
|
|
const gchar *value;
|
|
JsonNode *node;
|
|
|
|
g_return_val_if_fail (options != NULL, FALSE);
|
|
|
|
if (!cockpit_json_get_string (options, "channel", NULL, &value) || value != NULL)
|
|
{
|
|
g_message ("don't specify \"channel\" on external channel");
|
|
return FALSE;
|
|
}
|
|
if (!cockpit_json_get_string (options, "command", NULL, &value) || value != NULL)
|
|
{
|
|
g_message ("don't specify \"command\" on external channel");
|
|
return FALSE;
|
|
}
|
|
|
|
node = json_object_get_member (options, "external");
|
|
if (node == NULL)
|
|
{
|
|
if (content_disposition)
|
|
*content_disposition = NULL;
|
|
if (content_type)
|
|
*content_type = NULL;
|
|
if (content_encoding)
|
|
*content_encoding = NULL;
|
|
if (protocols)
|
|
*protocols = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
if (!JSON_NODE_HOLDS_OBJECT (node))
|
|
{
|
|
g_message ("invalid \"external\" option");
|
|
return FALSE;
|
|
}
|
|
|
|
external = json_node_get_object (node);
|
|
|
|
if (!cockpit_json_get_string (external, "content-disposition", NULL, &value) ||
|
|
(value && !cockpit_web_response_is_header_value (value)))
|
|
{
|
|
g_message ("invalid \"content-disposition\" external option");
|
|
return FALSE;
|
|
}
|
|
if (content_disposition)
|
|
*content_disposition = value;
|
|
|
|
if (!cockpit_json_get_string (external, "content-type", NULL, &value) ||
|
|
(value && !cockpit_web_response_is_header_value (value)))
|
|
{
|
|
g_message ("invalid \"content-type\" external option");
|
|
return FALSE;
|
|
}
|
|
if (content_type)
|
|
*content_type = value;
|
|
|
|
if (!cockpit_json_get_string (external, "content-encoding", NULL, &value) ||
|
|
(value && !cockpit_web_response_is_header_value (value)))
|
|
{
|
|
g_message ("invalid \"content-encoding\" external option");
|
|
return FALSE;
|
|
}
|
|
if (content_encoding)
|
|
*content_encoding = value;
|
|
|
|
if (!cockpit_json_get_strv (external, "protocols", NULL, protocols))
|
|
{
|
|
g_message ("invalid \"protocols\" external option");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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 gboolean
|
|
process_logout (CockpitWebService *self,
|
|
JsonObject *options)
|
|
{
|
|
gboolean disconnect;
|
|
|
|
if (!cockpit_json_get_bool (options, "disconnect", FALSE, &disconnect))
|
|
{
|
|
g_warning ("received 'logout' command with invalid 'disconnect' field");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Makes the credentials unusable */
|
|
cockpit_creds_poison (self->creds);
|
|
|
|
/* Destroys our web service, disconnects everything */
|
|
if (disconnect)
|
|
{
|
|
g_info ("Logging out session from %s", cockpit_creds_get_rhost (self->creds));
|
|
g_object_run_dispose (G_OBJECT (self));
|
|
}
|
|
else
|
|
{
|
|
g_info ("Deauthorizing session from %s", cockpit_creds_get_rhost (self->creds));
|
|
}
|
|
|
|
send_socket_hints (self, "credential", "none");
|
|
return TRUE;
|
|
}
|
|
|
|
static const gchar *
|
|
process_socket_init (CockpitWebService *self,
|
|
CockpitSocket *socket,
|
|
JsonObject *options)
|
|
{
|
|
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 web socket init message");
|
|
if (socket->init_received)
|
|
json_object_unref (socket->init_received);
|
|
socket->init_received = json_object_ref (options);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
g_message ("web socket used unsupported version of cockpit protocol: %"
|
|
G_GINT64_FORMAT, version);
|
|
return "not-supported";
|
|
}
|
|
}
|
|
|
|
static void
|
|
inbound_protocol_error (CockpitWebService *self,
|
|
WebSocketConnection *connection,
|
|
const gchar *problem)
|
|
{
|
|
GBytes *payload;
|
|
|
|
if (problem == NULL)
|
|
problem = "protocol-error";
|
|
|
|
if (web_socket_connection_get_ready_state (connection) == WEB_SOCKET_STATE_OPEN)
|
|
{
|
|
payload = cockpit_transport_build_control ("command", "close", "problem", problem, NULL);
|
|
web_socket_connection_send (connection, WEB_SOCKET_DATA_TEXT, self->control_prefix, payload);
|
|
g_bytes_unref (payload);
|
|
web_socket_connection_close (connection, WEB_SOCKET_CLOSE_SERVER_ERROR, problem);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatch_inbound_command (CockpitWebService *self,
|
|
CockpitSocket *socket,
|
|
GBytes *payload)
|
|
{
|
|
const gchar *problem = "protocol-error";
|
|
const gchar *command;
|
|
const gchar *channel;
|
|
JsonObject *options = NULL;
|
|
gboolean valid = FALSE;
|
|
|
|
valid = cockpit_transport_parse_command (payload, &command, &channel, &options);
|
|
if (!valid)
|
|
goto out;
|
|
|
|
if (g_strcmp0 (command, "init") == 0)
|
|
{
|
|
problem = process_socket_init (self, socket, options);
|
|
valid = (problem == NULL);
|
|
goto out;
|
|
}
|
|
|
|
if (!socket->init_received)
|
|
{
|
|
g_message ("web socket did not send 'init' message first");
|
|
valid = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
valid = TRUE;
|
|
|
|
if (g_strcmp0 (command, "open") == 0)
|
|
{
|
|
valid = process_and_relay_open (self, socket, channel, options);
|
|
}
|
|
else if (g_strcmp0 (command, "authorize") == 0)
|
|
{
|
|
valid = process_socket_authorize (self, socket, channel, options, payload);
|
|
}
|
|
else if (g_strcmp0 (command, "logout") == 0)
|
|
{
|
|
valid = process_logout (self, options);
|
|
if (valid)
|
|
{
|
|
/* logout is broadcast to everyone */
|
|
if (!self->sent_done)
|
|
cockpit_transport_send (self->transport, NULL, payload);
|
|
}
|
|
}
|
|
else if (g_strcmp0 (command, "close") == 0)
|
|
{
|
|
if (channel == NULL)
|
|
{
|
|
g_warning ("got close command without a channel");
|
|
valid = FALSE;
|
|
}
|
|
else
|
|
{
|
|
valid = process_and_relay_close (self, socket, channel, payload);
|
|
}
|
|
}
|
|
else if (g_strcmp0 (command, "kill") == 0)
|
|
{
|
|
valid = process_kill (self, socket, options, payload);
|
|
}
|
|
else if (!channel && g_strcmp0 (command, "ping") == 0)
|
|
{
|
|
valid = process_ping (self, socket, options);
|
|
}
|
|
else if (channel)
|
|
{
|
|
/* Relay anything with a channel by default */
|
|
if (!self->sent_done)
|
|
cockpit_transport_send (self->transport, NULL, payload);
|
|
}
|
|
|
|
out:
|
|
if (!valid)
|
|
inbound_protocol_error (self, socket->connection, problem);
|
|
if (options)
|
|
json_object_unref (options);
|
|
}
|
|
|
|
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
|
|
on_web_socket_open (WebSocketConnection *connection,
|
|
CockpitWebService *self)
|
|
{
|
|
CockpitSocket *socket;
|
|
JsonArray *capabilities;
|
|
GBytes *command;
|
|
JsonObject *object;
|
|
JsonObject *info;
|
|
|
|
g_info ("New connection to session from %s", cockpit_creds_get_rhost (self->creds));
|
|
|
|
socket = cockpit_socket_lookup_by_connection (&self->sockets, connection);
|
|
g_return_if_fail (socket != NULL);
|
|
|
|
object = json_object_new ();
|
|
json_object_set_string_member (object, "command", "init");
|
|
json_object_set_int_member (object, "version", 1);
|
|
json_object_set_string_member (object, "channel-seed", socket->id);
|
|
json_object_set_string_member (object, "host", "localhost");
|
|
json_object_set_string_member (object, "csrf-token", cockpit_creds_get_csrf_token (self->creds));
|
|
|
|
capabilities = json_array_new ();
|
|
json_array_add_string_element (capabilities, "multi");
|
|
json_array_add_string_element (capabilities, "credentials");
|
|
json_array_add_string_element (capabilities, "binary");
|
|
json_object_set_array_member (object, "capabilities", capabilities);
|
|
|
|
info = json_object_new ();
|
|
json_object_set_string_member (info, "version", PACKAGE_VERSION);
|
|
json_object_set_string_member (info, "build", COCKPIT_BUILD_INFO);
|
|
json_object_set_object_member (object, "system", info);
|
|
|
|
command = cockpit_json_write_bytes (object);
|
|
json_object_unref (object);
|
|
|
|
web_socket_connection_send (connection, WEB_SOCKET_DATA_TEXT, self->control_prefix, command);
|
|
g_bytes_unref (command);
|
|
|
|
/* Do we have an authorize password? if so tell the frontend */
|
|
if (cockpit_creds_get_password (self->creds))
|
|
send_socket_hints (self, "credential", "password");
|
|
|
|
g_signal_connect (connection, "message",
|
|
G_CALLBACK (on_web_socket_message), self);
|
|
}
|
|
|
|
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
|
|
on_web_socket_close (WebSocketConnection *connection,
|
|
CockpitWebService *self)
|
|
{
|
|
CockpitSocket *socket;
|
|
|
|
g_info ("WebSocket from %s for session closed", cockpit_creds_get_rhost (self->creds));
|
|
|
|
g_signal_handlers_disconnect_by_func (connection, on_web_socket_open, self);
|
|
g_signal_handlers_disconnect_by_func (connection, on_web_socket_closing, self);
|
|
g_signal_handlers_disconnect_by_func (connection, on_web_socket_close, self);
|
|
|
|
socket = cockpit_socket_lookup_by_connection (&self->sockets, connection);
|
|
g_return_if_fail (socket != NULL);
|
|
|
|
cockpit_socket_destroy (&self->sockets, socket);
|
|
|
|
caller_end (self);
|
|
}
|
|
|
|
static gboolean
|
|
on_ping_time (gpointer user_data)
|
|
{
|
|
CockpitWebService *self = user_data;
|
|
WebSocketConnection *connection;
|
|
GHashTableIter iter;
|
|
GBytes *payload;
|
|
|
|
payload = cockpit_transport_build_control ("command", "ping", NULL);
|
|
|
|
g_hash_table_iter_init (&iter, self->sockets.by_connection);
|
|
while (g_hash_table_iter_next (&iter, (gpointer *)&connection, NULL))
|
|
{
|
|
if (web_socket_connection_get_ready_state (connection) == WEB_SOCKET_STATE_OPEN)
|
|
web_socket_connection_send (connection, WEB_SOCKET_DATA_TEXT, self->control_prefix, payload);
|
|
}
|
|
|
|
g_bytes_unref (payload);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cockpit_web_service_init (CockpitWebService *self)
|
|
{
|
|
self->control_prefix = g_bytes_new_static ("\n", 1);
|
|
cockpit_sockets_init (&self->sockets);
|
|
self->ping_timeout = g_timeout_add_seconds (cockpit_ws_ping_interval, on_ping_time, self);
|
|
self->host_by_checksum = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
self->checksum_by_host = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
}
|
|
|
|
static void
|
|
cockpit_web_service_class_init (CockpitWebServiceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->dispose = cockpit_web_service_dispose;
|
|
object_class->finalize = cockpit_web_service_finalize;
|
|
|
|
sig_idling = g_signal_new ("idling", COCKPIT_TYPE_WEB_SERVICE,
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
sig_destroy = g_signal_new ("destroy", COCKPIT_TYPE_WEB_SERVICE,
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
/**
|
|
* cockpit_web_service_new:
|
|
* @creds: credentials of user
|
|
* @transport: an new cockpit transport that has not yet
|
|
* sent an init message.
|
|
*
|
|
* Creates a new web service to serve web sockets and pass
|
|
* messages to the given bridge.
|
|
*
|
|
* Returns: (transfer full): the new web service
|
|
*/
|
|
CockpitWebService *
|
|
cockpit_web_service_new (CockpitCreds *creds,
|
|
CockpitTransport *transport)
|
|
{
|
|
CockpitWebService *self;
|
|
|
|
g_return_val_if_fail (creds != NULL, NULL);
|
|
g_return_val_if_fail (transport != NULL, NULL);
|
|
|
|
self = g_object_new (COCKPIT_TYPE_WEB_SERVICE, NULL);
|
|
self->creds = cockpit_creds_ref (creds);
|
|
self->transport = g_object_ref (transport);
|
|
|
|
self->control_sig = g_signal_connect_after (self->transport, "control", G_CALLBACK (on_transport_control), self);
|
|
self->recv_sig = g_signal_connect_after (self->transport, "recv", G_CALLBACK (on_transport_recv), self);
|
|
self->closed_sig = g_signal_connect_after (self->transport, "closed", G_CALLBACK (on_transport_closed), self);
|
|
|
|
return self;
|
|
}
|
|
|
|
WebSocketConnection *
|
|
cockpit_web_service_create_socket (const gchar **protocols,
|
|
const gchar *path,
|
|
GIOStream *io_stream,
|
|
GHashTable *headers,
|
|
GByteArray *input_buffer,
|
|
gboolean for_tls_proxy)
|
|
{
|
|
WebSocketConnection *connection;
|
|
const gchar *host = NULL;
|
|
const gchar *protocol = NULL;
|
|
const gchar **origins;
|
|
gchar *allocated = NULL;
|
|
gchar *origin = NULL;
|
|
gchar *defaults[2];
|
|
gboolean is_https;
|
|
gchar *url;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
if (headers)
|
|
host = g_hash_table_lookup (headers, "Host");
|
|
if (!host)
|
|
host = cockpit_ws_default_host_header;
|
|
|
|
/* No headers case for tests */
|
|
if (cockpit_ws_default_protocol_header && !headers &&
|
|
cockpit_conf_string ("WebService", "ProtocolHeader"))
|
|
{
|
|
protocol = cockpit_ws_default_protocol_header;
|
|
}
|
|
else
|
|
{
|
|
protocol = cockpit_connection_get_protocol (io_stream, headers);
|
|
}
|
|
|
|
g_debug("cockpit_web_service_create_socket: host %s, protocol %s, for_tls_proxy %i", host, protocol, for_tls_proxy);
|
|
|
|
is_https = g_strcmp0 (protocol, "https") == 0 || for_tls_proxy;
|
|
|
|
url = g_strdup_printf ("%s://%s%s",
|
|
is_https ? "wss" : "ws",
|
|
host ? host : "localhost",
|
|
path);
|
|
|
|
origins = cockpit_conf_strv ("WebService", "Origins", ' ');
|
|
if (origins == NULL)
|
|
{
|
|
origin = g_strdup_printf ("%s://%s", is_https ? "https" : "http", host);
|
|
defaults[0] = origin;
|
|
defaults[1] = NULL;
|
|
origins = (const gchar **)defaults;
|
|
}
|
|
|
|
connection = web_socket_server_new_for_stream (url, origins, protocols,
|
|
io_stream, headers, input_buffer);
|
|
g_free (allocated);
|
|
g_free (url);
|
|
g_free (origin);
|
|
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* cockpit_web_service_socket:
|
|
* @io_stream: the stream to talk on
|
|
* @headers: optional headers already parsed
|
|
* @input_buffer: optional bytes already parsed after headers
|
|
* @auth: authentication object
|
|
* @creds: credentials of user or NULL for failed auth
|
|
* @for_tls_proxy: Assume that the Browser is making TLS connections that are terminated
|
|
* in a reverse proxy in front of cockpit-ws
|
|
*
|
|
* Serves the WebSocket on the given web service. Holds an extra
|
|
* reference to the web service until the socket is closed.
|
|
*/
|
|
void
|
|
cockpit_web_service_socket (CockpitWebService *self,
|
|
const gchar *path,
|
|
GIOStream *io_stream,
|
|
GHashTable *headers,
|
|
GByteArray *input_buffer,
|
|
gboolean for_tls_proxy)
|
|
{
|
|
const gchar *protocols[] = { "cockpit1", NULL };
|
|
WebSocketConnection *connection;
|
|
|
|
connection = cockpit_web_service_create_socket (protocols, path, io_stream, headers, input_buffer, for_tls_proxy);
|
|
|
|
g_signal_connect (connection, "open", G_CALLBACK (on_web_socket_open), self);
|
|
g_signal_connect (connection, "closing", G_CALLBACK (on_web_socket_closing), self);
|
|
g_signal_connect (connection, "close", G_CALLBACK (on_web_socket_close), self);
|
|
|
|
cockpit_socket_track (&self->sockets, connection);
|
|
g_object_unref (connection);
|
|
|
|
caller_begin (self);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* cockpit_web_service_get_creds:
|
|
* @self: the service
|
|
*
|
|
* Returns: (transfer none): the credentials for which this service was opened.
|
|
*/
|
|
CockpitCreds *
|
|
cockpit_web_service_get_creds (CockpitWebService *self)
|
|
{
|
|
g_return_val_if_fail (COCKPIT_IS_WEB_SERVICE (self), NULL);
|
|
return self->creds;
|
|
}
|
|
|
|
/**
|
|
* cockpit_web_service_disconnect:
|
|
* @self: the service
|
|
*
|
|
* Close all sockets that are running in this web
|
|
* service.
|
|
*/
|
|
void
|
|
cockpit_web_service_disconnect (CockpitWebService *self)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (self));
|
|
}
|
|
|
|
gboolean
|
|
cockpit_web_service_get_idling (CockpitWebService *self)
|
|
{
|
|
g_return_val_if_fail (COCKPIT_IS_WEB_SERVICE (self), TRUE);
|
|
return (self->callers == 0);
|
|
}
|
|
|
|
CockpitTransport *
|
|
cockpit_web_service_get_transport (CockpitWebService *self)
|
|
{
|
|
g_return_val_if_fail (COCKPIT_IS_WEB_SERVICE (self), NULL);
|
|
return self->transport;
|
|
}
|
|
|
|
JsonObject *
|
|
cockpit_web_service_get_init (CockpitWebService *self)
|
|
{
|
|
g_return_val_if_fail (COCKPIT_IS_WEB_SERVICE (self), NULL);
|
|
return self->init_received;
|
|
}
|
|
|
|
const gchar *
|
|
cockpit_web_service_get_host (CockpitWebService *self,
|
|
const gchar *checksum)
|
|
{
|
|
return g_hash_table_lookup (self->host_by_checksum, checksum);
|
|
}
|
|
|
|
const gchar *
|
|
cockpit_web_service_get_checksum (CockpitWebService *self,
|
|
const gchar *host)
|
|
{
|
|
return g_hash_table_lookup (self->checksum_by_host, host);
|
|
}
|
|
|
|
void
|
|
cockpit_web_service_set_host_checksum (CockpitWebService *self,
|
|
const gchar *host,
|
|
const gchar *checksum)
|
|
{
|
|
const gchar *old_checksum = g_hash_table_lookup (self->checksum_by_host, host);
|
|
const gchar *old_host = g_hash_table_lookup (self->host_by_checksum, checksum);
|
|
|
|
if (g_strcmp0 (checksum, old_checksum) == 0)
|
|
return;
|
|
|
|
if (old_checksum)
|
|
g_hash_table_remove (self->host_by_checksum, old_checksum);
|
|
|
|
/* Only replace checksum if the old one wasn't localhost */
|
|
if (g_strcmp0 (old_host, "localhost") != 0)
|
|
g_hash_table_replace (self->host_by_checksum, g_strdup (checksum), g_strdup (host));
|
|
|
|
g_hash_table_replace (self->checksum_by_host, g_strdup (host), g_strdup (checksum));
|
|
}
|