summaryrefslogtreecommitdiff
path: root/gtk/spice-channel.c
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2015-06-05 17:44:47 +0200
committerMarc-André Lureau <marcandre.lureau@redhat.com>2015-06-08 17:38:58 +0200
commitcaf28401cac9ece5e314360f8a3a54479df59727 (patch)
tree7ada0451442ffa1d9d056500ebd878ad80da3e06 /gtk/spice-channel.c
parent39c315241350dde3741c99fee4fff17b22d2b1fa (diff)
Move gtk/ -> src/
For historical reasons, the code was placed under gtk/ subdirectory. If it was always bugging you, bug no more!
Diffstat (limited to 'gtk/spice-channel.c')
-rw-r--r--gtk/spice-channel.c2960
1 files changed, 0 insertions, 2960 deletions
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
deleted file mode 100644
index 4e7d8b7..0000000
--- a/gtk/spice-channel.c
+++ /dev/null
@@ -1,2960 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- Copyright (C) 2010 Red Hat, Inc.
-
- This library 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.
-
- This library 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 this library; if not, see <http://www.gnu.org/licenses/>.
-*/
-#include "config.h"
-
-#include "spice-client.h"
-#include "spice-common.h"
-#include "glib-compat.h"
-
-#include "spice-channel-priv.h"
-#include "spice-session-priv.h"
-#include "spice-marshal.h"
-#include "bio-gio.h"
-
-#include <glib/gi18n.h>
-
-#include <openssl/rsa.h>
-#include <openssl/evp.h>
-#include <openssl/x509.h>
-#include <openssl/ssl.h>
-#include <openssl/err.h>
-#include <openssl/x509v3.h>
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#include <netinet/tcp.h> // TCP_NODELAY
-#endif
-#ifdef HAVE_ARPA_INET_H
-#include <arpa/inet.h>
-#endif
-#include <ctype.h>
-
-#include "gio-coroutine.h"
-
-static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
-static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out);
-static void spice_channel_send_link(SpiceChannel *channel);
-static void channel_reset(SpiceChannel *channel, gboolean migrating);
-static void spice_channel_reset_capabilities(SpiceChannel *channel);
-static void spice_channel_send_migration_handshake(SpiceChannel *channel);
-static gboolean channel_connect(SpiceChannel *channel, gboolean tls);
-
-/**
- * SECTION:spice-channel
- * @short_description: the base channel class
- * @title: Spice Channel
- * @section_id:
- * @see_also: #SpiceSession, #SpiceMainChannel and other channels
- * @stability: Stable
- * @include: spice-channel.h
- *
- * #SpiceChannel is the base class for the different kind of Spice
- * channel connections, such as #SpiceMainChannel, or
- * #SpiceInputsChannel.
- */
-
-/* ------------------------------------------------------------------ */
-/* gobject glue */
-
-#define SPICE_CHANNEL_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_CHANNEL, SpiceChannelPrivate))
-
-G_DEFINE_TYPE(SpiceChannel, spice_channel, G_TYPE_OBJECT);
-
-/* Properties */
-enum {
- PROP_0,
- PROP_SESSION,
- PROP_CHANNEL_TYPE,
- PROP_CHANNEL_ID,
- PROP_TOTAL_READ_BYTES,
-};
-
-/* Signals */
-enum {
- SPICE_CHANNEL_EVENT,
- SPICE_CHANNEL_OPEN_FD,
-
- SPICE_CHANNEL_LAST_SIGNAL,
-};
-
-static guint signals[SPICE_CHANNEL_LAST_SIGNAL];
-
-static void spice_channel_iterate_write(SpiceChannel *channel);
-static void spice_channel_iterate_read(SpiceChannel *channel);
-
-static void spice_channel_init(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c;
-
- c = channel->priv = SPICE_CHANNEL_GET_PRIVATE(channel);
-
- c->out_serial = 1;
- c->in_serial = 1;
- c->fd = -1;
- c->auth_needs_username_and_password = FALSE;
- strcpy(c->name, "?");
- c->caps = g_array_new(FALSE, TRUE, sizeof(guint32));
- c->common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
- c->remote_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
- c->remote_common_caps = g_array_new(FALSE, TRUE, sizeof(guint32));
- spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
- spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
-#if HAVE_SASL
- spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL);
-#endif
- g_queue_init(&c->xmit_queue);
- STATIC_MUTEX_INIT(c->xmit_queue_lock);
-}
-
-static void spice_channel_constructed(GObject *gobject)
-{
- SpiceChannel *channel = SPICE_CHANNEL(gobject);
- SpiceChannelPrivate *c = channel->priv;
- const char *desc = spice_channel_type_to_string(c->channel_type);
-
- snprintf(c->name, sizeof(c->name), "%s-%d:%d",
- desc, c->channel_type, c->channel_id);
- CHANNEL_DEBUG(channel, "%s", __FUNCTION__);
-
- const char *disabled = g_getenv("SPICE_DISABLE_CHANNELS");
- if (disabled && strstr(disabled, desc))
- c->disable_channel_msg = TRUE;
-
- spice_session_channel_new(c->session, channel);
-
- /* Chain up to the parent class */
- if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed)
- G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject);
-}
-
-static void spice_channel_dispose(GObject *gobject)
-{
- SpiceChannel *channel = SPICE_CHANNEL(gobject);
- SpiceChannelPrivate *c = channel->priv;
-
- CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
-
- spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
-
- if (c->session) {
- g_object_unref(c->session);
- c->session = NULL;
- }
-
- g_clear_error(&c->error);
-
- /* Chain up to the parent class */
- if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose)
- G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject);
-}
-
-static void spice_channel_finalize(GObject *gobject)
-{
- SpiceChannel *channel = SPICE_CHANNEL(gobject);
- SpiceChannelPrivate *c = channel->priv;
-
- CHANNEL_DEBUG(channel, "%s %p", __FUNCTION__, gobject);
-
- g_idle_remove_by_data(gobject);
-
- STATIC_MUTEX_CLEAR(c->xmit_queue_lock);
-
- if (c->caps)
- g_array_free(c->caps, TRUE);
-
- if (c->common_caps)
- g_array_free(c->common_caps, TRUE);
-
- if (c->remote_caps)
- g_array_free(c->remote_caps, TRUE);
-
- if (c->remote_common_caps)
- g_array_free(c->remote_common_caps, TRUE);
-
- /* Chain up to the parent class */
- if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize)
- G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject);
-}
-
-static void spice_channel_get_property(GObject *gobject,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- SpiceChannel *channel = SPICE_CHANNEL(gobject);
- SpiceChannelPrivate *c = channel->priv;
-
- switch (prop_id) {
- case PROP_SESSION:
- g_value_set_object(value, c->session);
- break;
- case PROP_CHANNEL_TYPE:
- g_value_set_int(value, c->channel_type);
- break;
- case PROP_CHANNEL_ID:
- g_value_set_int(value, c->channel_id);
- break;
- case PROP_TOTAL_READ_BYTES:
- g_value_set_ulong(value, c->total_read_bytes);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
- break;
- }
-}
-
-G_GNUC_INTERNAL
-gint spice_channel_get_channel_id(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- g_return_val_if_fail(c != NULL, 0);
- return c->channel_id;
-}
-
-G_GNUC_INTERNAL
-gint spice_channel_get_channel_type(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- g_return_val_if_fail(c != NULL, 0);
- return c->channel_type;
-}
-
-static void spice_channel_set_property(GObject *gobject,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- SpiceChannel *channel = SPICE_CHANNEL(gobject);
- SpiceChannelPrivate *c = channel->priv;
-
- switch (prop_id) {
- case PROP_SESSION:
- c->session = g_value_dup_object(value);
- break;
- case PROP_CHANNEL_TYPE:
- c->channel_type = g_value_get_int(value);
- break;
- case PROP_CHANNEL_ID:
- c->channel_id = g_value_get_int(value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
- break;
- }
-}
-
-static void spice_channel_class_init(SpiceChannelClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
- klass->iterate_write = spice_channel_iterate_write;
- klass->iterate_read = spice_channel_iterate_read;
- klass->channel_reset = channel_reset;
-
- gobject_class->constructed = spice_channel_constructed;
- gobject_class->dispose = spice_channel_dispose;
- gobject_class->finalize = spice_channel_finalize;
- gobject_class->get_property = spice_channel_get_property;
- gobject_class->set_property = spice_channel_set_property;
- klass->handle_msg = spice_channel_handle_msg;
-
- g_object_class_install_property
- (gobject_class, PROP_SESSION,
- g_param_spec_object("spice-session",
- "Spice session",
- "",
- SPICE_TYPE_SESSION,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_property
- (gobject_class, PROP_CHANNEL_TYPE,
- g_param_spec_int("channel-type",
- "Channel type",
- "",
- -1, INT_MAX, -1,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_property
- (gobject_class, PROP_CHANNEL_ID,
- g_param_spec_int("channel-id",
- "Channel ID",
- "",
- -1, INT_MAX, -1,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_property
- (gobject_class, PROP_TOTAL_READ_BYTES,
- g_param_spec_ulong("total-read-bytes",
- "Total read bytes",
- "",
- 0, G_MAXULONG, 0,
- G_PARAM_READABLE |
- G_PARAM_STATIC_STRINGS));
-
- /**
- * SpiceChannel::channel-event:
- * @channel: the channel that emitted the signal
- * @event: a #SpiceChannelEvent
- *
- * The #SpiceChannel::channel-event signal is emitted when the
- * state of the connection is changed. In case of errors,
- * spice_channel_get_error() may provide additional informations
- * on the source of the error.
- **/
- signals[SPICE_CHANNEL_EVENT] =
- g_signal_new("channel-event",
- G_OBJECT_CLASS_TYPE(gobject_class),
- G_SIGNAL_RUN_FIRST,
- G_STRUCT_OFFSET(SpiceChannelClass, channel_event),
- NULL, NULL,
- g_cclosure_marshal_VOID__ENUM,
- G_TYPE_NONE,
- 1,
- SPICE_TYPE_CHANNEL_EVENT);
-
- /**
- * SpiceChannel::open-fd:
- * @channel: the channel that emitted the signal
- * @with_tls: wether TLS connection is requested
- *
- * The #SpiceChannel::open-fd signal is emitted when a new
- * connection is requested. This signal is emitted when the
- * connection is made with spice_session_open_fd().
- **/
- signals[SPICE_CHANNEL_OPEN_FD] =
- g_signal_new("open-fd",
- G_OBJECT_CLASS_TYPE(gobject_class),
- G_SIGNAL_RUN_FIRST,
- G_STRUCT_OFFSET(SpiceChannelClass, open_fd),
- NULL, NULL,
- g_cclosure_marshal_VOID__INT,
- G_TYPE_NONE,
- 1,
- G_TYPE_INT);
-
- g_type_class_add_private(klass, sizeof(SpiceChannelPrivate));
-
- SSL_library_init();
- SSL_load_error_strings();
-}
-
-/* ---------------------------------------------------------------- */
-/* private header api */
-
-static inline void spice_header_set_msg_type(uint8_t *header, gboolean is_mini_header,
- uint16_t type)
-{
- if (is_mini_header) {
- ((SpiceMiniDataHeader *)header)->type = type;
- } else {
- ((SpiceDataHeader *)header)->type = type;
- }
-}
-
-static inline void spice_header_set_msg_size(uint8_t *header, gboolean is_mini_header,
- uint32_t size)
-{
- if (is_mini_header) {
- ((SpiceMiniDataHeader *)header)->size = size;
- } else {
- ((SpiceDataHeader *)header)->size = size;
- }
-}
-
-G_GNUC_INTERNAL
-uint16_t spice_header_get_msg_type(uint8_t *header, gboolean is_mini_header)
-{
- if (is_mini_header) {
- return ((SpiceMiniDataHeader *)header)->type;
- } else {
- return ((SpiceDataHeader *)header)->type;
- }
-}
-
-G_GNUC_INTERNAL
-uint32_t spice_header_get_msg_size(uint8_t *header, gboolean is_mini_header)
-{
- if (is_mini_header) {
- return ((SpiceMiniDataHeader *)header)->size;
- } else {
- return ((SpiceDataHeader *)header)->size;
- }
-}
-
-static inline int spice_header_get_header_size(gboolean is_mini_header)
-{
- return is_mini_header ? sizeof(SpiceMiniDataHeader) : sizeof(SpiceDataHeader);
-}
-
-static inline void spice_header_set_msg_serial(uint8_t *header, gboolean is_mini_header,
- uint64_t serial)
-{
- if (!is_mini_header) {
- ((SpiceDataHeader *)header)->serial = serial;
- }
-}
-
-static inline void spice_header_reset_msg_sub_list(uint8_t *header, gboolean is_mini_header)
-{
- if (!is_mini_header) {
- ((SpiceDataHeader *)header)->sub_list = 0;
- }
-}
-
-static inline uint64_t spice_header_get_in_msg_serial(SpiceMsgIn *in)
-{
- SpiceChannelPrivate *c = in->channel->priv;
- uint8_t *header = in->header;
-
- if (c->use_mini_header) {
- return c->in_serial;
- } else {
- return ((SpiceDataHeader *)header)->serial;
- }
-}
-
-static inline uint64_t spice_header_get_out_msg_serial(SpiceMsgOut *out)
-{
- SpiceChannelPrivate *c = out->channel->priv;
-
- if (c->use_mini_header) {
- return c->out_serial;
- } else {
- return ((SpiceDataHeader *)out->header)->serial;
- }
-}
-
-static inline uint32_t spice_header_get_msg_sub_list(uint8_t *header, gboolean is_mini_header)
-{
- if (is_mini_header) {
- return 0;
- } else {
- return ((SpiceDataHeader *)header)->sub_list;
- }
-}
-
-/* ---------------------------------------------------------------- */
-/* private msg api */
-
-G_GNUC_INTERNAL
-SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel)
-{
- SpiceMsgIn *in;
-
- g_return_val_if_fail(channel != NULL, NULL);
-
- in = g_slice_new0(SpiceMsgIn);
- in->refcount = 1;
- in->channel = channel;
-
- return in;
-}
-
-G_GNUC_INTERNAL
-SpiceMsgIn *spice_msg_in_sub_new(SpiceChannel *channel, SpiceMsgIn *parent,
- SpiceSubMessage *sub)
-{
- SpiceMsgIn *in;
-
- g_return_val_if_fail(channel != NULL, NULL);
-
- in = spice_msg_in_new(channel);
- spice_header_set_msg_type(in->header, channel->priv->use_mini_header, sub->type);
- spice_header_set_msg_size(in->header, channel->priv->use_mini_header, sub->size);
- in->data = (uint8_t*)(sub+1);
- in->dpos = sub->size;
- in->parent = parent;
- spice_msg_in_ref(parent);
- return in;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_ref(SpiceMsgIn *in)
-{
- g_return_if_fail(in != NULL);
-
- in->refcount++;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_unref(SpiceMsgIn *in)
-{
- g_return_if_fail(in != NULL);
-
- in->refcount--;
- if (in->refcount > 0)
- return;
- if (in->parsed)
- in->pfree(in->parsed);
- if (in->parent) {
- spice_msg_in_unref(in->parent);
- } else {
- g_free(in->data);
- }
- g_slice_free(SpiceMsgIn, in);
-}
-
-G_GNUC_INTERNAL
-int spice_msg_in_type(SpiceMsgIn *in)
-{
- g_return_val_if_fail(in != NULL, -1);
-
- return spice_header_get_msg_type(in->header, in->channel->priv->use_mini_header);
-}
-
-G_GNUC_INTERNAL
-void *spice_msg_in_parsed(SpiceMsgIn *in)
-{
- g_return_val_if_fail(in != NULL, NULL);
-
- return in->parsed;
-}
-
-G_GNUC_INTERNAL
-void *spice_msg_in_raw(SpiceMsgIn *in, int *len)
-{
- g_return_val_if_fail(in != NULL, NULL);
- g_return_val_if_fail(len != NULL, NULL);
-
- *len = in->dpos;
- return in->data;
-}
-
-static void hexdump(const char *prefix, unsigned char *data, int len)
-{
- int i;
-
- for (i = 0; i < len; i++) {
- if (i % 16 == 0)
- fprintf(stderr, "%s:", prefix);
- if (i % 4 == 0)
- fprintf(stderr, " ");
- fprintf(stderr, " %02x", data[i]);
- if (i % 16 == 15)
- fprintf(stderr, "\n");
- }
- if (i % 16 != 0)
- fprintf(stderr, "\n");
-}
-
-G_GNUC_INTERNAL
-void spice_msg_in_hexdump(SpiceMsgIn *in)
-{
- SpiceChannelPrivate *c = in->channel->priv;
-
- fprintf(stderr, "--\n<< hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
- c->name, spice_header_get_in_msg_serial(in),
- spice_header_get_msg_type(in->header, c->use_mini_header),
- spice_header_get_msg_size(in->header, c->use_mini_header),
- spice_header_get_msg_sub_list(in->header, c->use_mini_header));
- hexdump("<< msg", in->data, in->dpos);
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_hexdump(SpiceMsgOut *out, unsigned char *data, int len)
-{
- SpiceChannelPrivate *c = out->channel->priv;
-
- fprintf(stderr, "--\n>> hdr: %s serial %" PRIu64 " type %d size %d sub-list %d\n",
- c->name,
- spice_header_get_out_msg_serial(out),
- spice_header_get_msg_type(out->header, c->use_mini_header),
- spice_header_get_msg_size(out->header, c->use_mini_header),
- spice_header_get_msg_sub_list(out->header, c->use_mini_header));
- hexdump(">> msg", data, len);
-}
-
-static gboolean msg_check_read_only (int channel_type, int msg_type)
-{
- if (msg_type < 100) // those are the common messages
- return FALSE;
-
- switch (channel_type) {
- /* messages allowed to be sent in read-only mode */
- case SPICE_CHANNEL_MAIN:
- switch (msg_type) {
- case SPICE_MSGC_MAIN_CLIENT_INFO:
- case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
- case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
- case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
- case SPICE_MSGC_MAIN_MIGRATE_END:
- return FALSE;
- }
- break;
- case SPICE_CHANNEL_DISPLAY:
- return FALSE;
- }
-
- return TRUE;
-}
-
-G_GNUC_INTERNAL
-SpiceMsgOut *spice_msg_out_new(SpiceChannel *channel, int type)
-{
- SpiceChannelPrivate *c = channel->priv;
- SpiceMsgOut *out;
-
- g_return_val_if_fail(c != NULL, NULL);
-
- out = g_slice_new0(SpiceMsgOut);
- out->refcount = 1;
- out->channel = channel;
- out->ro_check = msg_check_read_only(c->channel_type, type);
-
- out->marshallers = c->marshallers;
- out->marshaller = spice_marshaller_new();
-
- out->header = spice_marshaller_reserve_space(out->marshaller,
- spice_header_get_header_size(c->use_mini_header));
- spice_marshaller_set_base(out->marshaller, spice_header_get_header_size(c->use_mini_header));
- spice_header_set_msg_type(out->header, c->use_mini_header, type);
- spice_header_set_msg_serial(out->header, c->use_mini_header, c->out_serial);
- spice_header_reset_msg_sub_list(out->header, c->use_mini_header);
-
- c->out_serial++;
- return out;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_ref(SpiceMsgOut *out)
-{
- g_return_if_fail(out != NULL);
-
- out->refcount++;
-}
-
-G_GNUC_INTERNAL
-void spice_msg_out_unref(SpiceMsgOut *out)
-{
- g_return_if_fail(out != NULL);
-
- out->refcount--;
- if (out->refcount > 0)
- return;
- spice_marshaller_destroy(out->marshaller);
- g_slice_free(SpiceMsgOut, out);
-}
-
-/* system context */
-static gboolean spice_channel_idle_wakeup(gpointer user_data)
-{
- SpiceChannel *channel = SPICE_CHANNEL(user_data);
- SpiceChannelPrivate *c = channel->priv;
-
- /*
- * Note:
- *
- * - This must be done before the wakeup as that may eventually
- * call channel_reset() which checks this.
- * - The lock calls are really necessary, this fixes the following race:
- * 1) usb-event-thread calls spice_msg_out_send()
- * 2) spice_msg_out_send calls g_timeout_add_full(...)
- * 3) we run, set xmit_queue_wakeup_id to 0
- * 4) spice_msg_out_send stores the result of g_timeout_add_full() in
- * xmit_queue_wakeup_id, overwriting the 0 we just stored
- * 5) xmit_queue_wakeup_id now says there is a wakeup pending which is
- * false
- */
- STATIC_MUTEX_LOCK(c->xmit_queue_lock);
- c->xmit_queue_wakeup_id = 0;
- STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-
- spice_channel_wakeup(channel, FALSE);
-
- return FALSE;
-}
-
-/* any context (system/co-routine/usb-event-thread) */
-G_GNUC_INTERNAL
-void spice_msg_out_send(SpiceMsgOut *out)
-{
- SpiceChannelPrivate *c;
- gboolean was_empty;
-
- g_return_if_fail(out != NULL);
- g_return_if_fail(out->channel != NULL);
- c = out->channel->priv;
-
- STATIC_MUTEX_LOCK(c->xmit_queue_lock);
- if (c->xmit_queue_blocked) {
- g_warning("message queue is blocked, dropping message");
- goto end;
- }
-
- was_empty = g_queue_is_empty(&c->xmit_queue);
- g_queue_push_tail(&c->xmit_queue, out);
-
- /* One wakeup is enough to empty the entire queue -> only do a wakeup
- if the queue was empty, and there isn't one pending already. */
- if (was_empty && !c->xmit_queue_wakeup_id) {
- c->xmit_queue_wakeup_id =
- /* Use g_timeout_add_full so that can specify the priority */
- g_timeout_add_full(G_PRIORITY_HIGH, 0,
- spice_channel_idle_wakeup,
- out->channel, NULL);
- }
-
-end:
- STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_msg_out_send_internal(SpiceMsgOut *out)
-{
- g_return_if_fail(out != NULL);
-
- spice_channel_write_msg(out->channel, out);
-}
-
-/*
- * Write all 'data' of length 'datalen' bytes out to
- * the wire
- */
-/* coroutine context */
-static void spice_channel_flush_wire(SpiceChannel *channel,
- const void *data,
- size_t datalen)
-{
- SpiceChannelPrivate *c = channel->priv;
- const char *ptr = data;
- size_t offset = 0;
- GIOCondition cond;
-
- while (offset < datalen) {
- gssize ret;
- GError *error = NULL;
-
- if (c->has_error) return;
-
- cond = 0;
- if (c->tls) {
- ret = SSL_write(c->ssl, ptr+offset, datalen-offset);
- if (ret < 0) {
- ret = SSL_get_error(c->ssl, ret);
- if (ret == SSL_ERROR_WANT_READ)
- cond |= G_IO_IN;
- if (ret == SSL_ERROR_WANT_WRITE)
- cond |= G_IO_OUT;
- ret = -1;
- }
- } else {
- ret = g_pollable_output_stream_write_nonblocking(G_POLLABLE_OUTPUT_STREAM(c->out),
- ptr+offset, datalen-offset, NULL, &error);
- if (ret < 0) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
- cond = G_IO_OUT;
- } else {
- CHANNEL_DEBUG(channel, "Send error %s", error->message);
- }
- g_clear_error(&error);
- ret = -1;
- }
- }
- if (ret == -1) {
- if (cond != 0) {
- // TODO: should use g_pollable_input/output_stream_create_source() in 2.28 ?
- g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
- continue;
- } else {
- CHANNEL_DEBUG(channel, "Closing the channel: spice_channel_flush %d", errno);
- c->has_error = TRUE;
- return;
- }
- }
- if (ret == 0) {
- CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_flush");
- c->has_error = TRUE;
- return;
- }
- offset += ret;
- }
-}
-
-#if HAVE_SASL
-/*
- * Encode all buffered data, write all encrypted data out
- * to the wire
- */
-static void spice_channel_flush_sasl(SpiceChannel *channel, const void *data, size_t len)
-{
- SpiceChannelPrivate *c = channel->priv;
- const char *output;
- unsigned int outputlen;
- int err;
-
- err = sasl_encode(c->sasl_conn, data, len, &output, &outputlen);
- if (err != SASL_OK) {
- g_warning ("Failed to encode SASL data %s",
- sasl_errstring(err, NULL, NULL));
- c->has_error = TRUE;
- return;
- }
-
- //CHANNEL_DEBUG(channel, "Flush SASL %d: %p %d", len, output, outputlen);
- spice_channel_flush_wire(channel, output, outputlen);
-}
-#endif
-
-/* coroutine context */
-static void spice_channel_write(SpiceChannel *channel, const void *data, size_t len)
-{
-#if HAVE_SASL
- SpiceChannelPrivate *c = channel->priv;
-
- if (c->sasl_conn)
- spice_channel_flush_sasl(channel, data, len);
- else
-#endif
- spice_channel_flush_wire(channel, data, len);
-}
-
-/* coroutine context */
-static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out)
-{
- uint8_t *data;
- int free_data;
- size_t len;
- uint32_t msg_size;
-
- g_return_if_fail(channel != NULL);
- g_return_if_fail(out != NULL);
- g_return_if_fail(channel == out->channel);
-
- if (out->ro_check &&
- spice_channel_get_read_only(channel)) {
- g_warning("Try to send message while read-only. Please report a bug.");
- return;
- }
-
- msg_size = spice_marshaller_get_total_size(out->marshaller) -
- spice_header_get_header_size(channel->priv->use_mini_header);
- spice_header_set_msg_size(out->header, channel->priv->use_mini_header, msg_size);
- data = spice_marshaller_linearize(out->marshaller, 0, &len, &free_data);
- /* spice_msg_out_hexdump(out, data, len); */
- spice_channel_write(channel, data, len);
-
- if (free_data)
- g_free(data);
-
- spice_msg_out_unref(out);
-}
-
-/*
- * Read at least 1 more byte of data straight off the wire
- * into the requested buffer.
- */
-/* coroutine context */
-static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len)
-{
- SpiceChannelPrivate *c = channel->priv;
- gssize ret;
- GIOCondition cond;
-
-reread:
-
- if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
-
- cond = 0;
- if (c->tls) {
- ret = SSL_read(c->ssl, data, len);
- if (ret < 0) {
- ret = SSL_get_error(c->ssl, ret);
- if (ret == SSL_ERROR_WANT_READ)
- cond |= G_IO_IN;
- if (ret == SSL_ERROR_WANT_WRITE)
- cond |= G_IO_OUT;
- ret = -1;
- }
- } else {
- GError *error = NULL;
- ret = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(c->in),
- data, len, NULL, &error);
- if (ret < 0) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
- cond = G_IO_IN;
- } else {
- CHANNEL_DEBUG(channel, "Read error %s", error->message);
- }
- g_clear_error(&error);
- ret = -1;
- }
- }
-
- if (ret == -1) {
- if (cond != 0) {
- // TODO: should use g_pollable_input/output_stream_create_source() ?
- g_coroutine_socket_wait(&c->coroutine, c->sock, cond);
- goto reread;
- } else {
- c->has_error = TRUE;
- return -errno;
- }
- }
- if (ret == 0) {
- CHANNEL_DEBUG(channel, "Closing the connection: spice_channel_read() - ret=0");
- c->has_error = TRUE;
- return 0;
- }
-
- return ret;
-}
-
-#if HAVE_SASL
-/*
- * Read at least 1 more byte of data out of the SASL decrypted
- * data buffer, into the internal read buffer
- */
-static int spice_channel_read_sasl(SpiceChannel *channel, void *data, size_t len)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- /* CHANNEL_DEBUG(channel, "Read %lu SASL %p size %d offset %d", len, c->sasl_decoded, */
- /* c->sasl_decoded_length, c->sasl_decoded_offset); */
-
- if (c->sasl_decoded == NULL || c->sasl_decoded_length == 0) {
- char encoded[8192]; /* should stay lower than maxbufsize */
- int err, ret;
-
- g_warn_if_fail(c->sasl_decoded_offset == 0);
-
- ret = spice_channel_read_wire(channel, encoded, sizeof(encoded));
- if (ret < 0)
- return ret;
-
- err = sasl_decode(c->sasl_conn, encoded, ret,
- &c->sasl_decoded, &c->sasl_decoded_length);
- if (err != SASL_OK) {
- g_warning("Failed to decode SASL data %s",
- sasl_errstring(err, NULL, NULL));
- c->has_error = TRUE;
- return -EINVAL;
- }
- c->sasl_decoded_offset = 0;
- }
-
- if (c->sasl_decoded_length == 0)
- return 0;
-
- len = MIN(c->sasl_decoded_length - c->sasl_decoded_offset, len);
- memcpy(data, c->sasl_decoded + c->sasl_decoded_offset, len);
- c->sasl_decoded_offset += len;
-
- if (c->sasl_decoded_offset == c->sasl_decoded_length) {
- c->sasl_decoded_length = c->sasl_decoded_offset = 0;
- c->sasl_decoded = NULL;
- }
-
- return len;
-}
-#endif
-
-/*
- * Fill the 'data' buffer up with exactly 'len' bytes worth of data
- */
-/* coroutine context */
-static int spice_channel_read(SpiceChannel *channel, void *data, size_t length)
-{
- SpiceChannelPrivate *c = channel->priv;
- gsize len = length;
- int ret;
-
- while (len > 0) {
- if (c->has_error) return 0; /* has_error is set by disconnect(), return no error */
-
-#if HAVE_SASL
- if (c->sasl_conn)
- ret = spice_channel_read_sasl(channel, data, len);
- else
-#endif
- ret = spice_channel_read_wire(channel, data, len);
- if (ret < 0)
- return ret;
- g_assert(ret <= len);
- len -= ret;
- data = ((char*)data) + ret;
-#if DEBUG
- if (len > 0)
- CHANNEL_DEBUG(channel, "still needs %" G_GSIZE_FORMAT, len);
-#endif
- }
- c->total_read_bytes += length;
-
- return length;
-}
-
-/* coroutine context */
-static void spice_channel_send_spice_ticket(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- EVP_PKEY *pubkey;
- int nRSASize;
- BIO *bioKey;
- RSA *rsa;
- char *password;
- uint8_t *encrypted;
- int rc;
-
- bioKey = BIO_new(BIO_s_mem());
- g_return_if_fail(bioKey != NULL);
-
- BIO_write(bioKey, c->peer_msg->pub_key, SPICE_TICKET_PUBKEY_BYTES);
- pubkey = d2i_PUBKEY_bio(bioKey, NULL);
- g_return_if_fail(pubkey != NULL);
-
- rsa = pubkey->pkey.rsa;
- nRSASize = RSA_size(rsa);
-
- encrypted = g_alloca(nRSASize);
- /*
- The use of RSA encryption limit the potential maximum password length.
- for RSA_PKCS1_OAEP_PADDING it is RSA_size(rsa) - 41.
- */
- g_object_get(c->session, "password", &password, NULL);
- if (password == NULL)
- password = g_strdup("");
- rc = RSA_public_encrypt(strlen(password) + 1, (uint8_t*)password,
- encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
- g_warn_if_fail(rc > 0);
-
- spice_channel_write(channel, encrypted, nRSASize);
- memset(encrypted, 0, nRSASize);
- EVP_PKEY_free(pubkey);
- BIO_free(bioKey);
- g_free(password);
-}
-
-/* coroutine context */
-static void spice_channel_failed_authentication(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- if (c->auth_needs_username_and_password)
- g_set_error_literal(&c->error,
- SPICE_CLIENT_ERROR,
- SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD_AND_USERNAME,
- _("Authentication failed: password and username are required"));
- else
- g_set_error_literal(&c->error,
- SPICE_CLIENT_ERROR,
- SPICE_CLIENT_ERROR_AUTH_NEEDS_PASSWORD,
- _("Authentication failed: password is required"));
-
- c->event = SPICE_CHANNEL_ERROR_AUTH;
-
- c->has_error = TRUE; /* force disconnect */
-}
-
-/* coroutine context */
-static gboolean spice_channel_recv_auth(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- uint32_t link_res;
- int rc;
-
- rc = spice_channel_read(channel, &link_res, sizeof(link_res));
- if (rc != sizeof(link_res)) {
- CHANNEL_DEBUG(channel, "incomplete auth reply (%d/%" G_GSIZE_FORMAT ")",
- rc, sizeof(link_res));
- c->event = SPICE_CHANNEL_ERROR_LINK;
- return FALSE;
- }
-
- if (link_res != SPICE_LINK_ERR_OK) {
- CHANNEL_DEBUG(channel, "link result: reply %d", link_res);
- spice_channel_failed_authentication(channel);
- return FALSE;
- }
-
- c->state = SPICE_CHANNEL_STATE_READY;
-
- g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_OPENED);
-
- if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
- spice_channel_send_migration_handshake(channel);
- }
-
- if (c->state != SPICE_CHANNEL_STATE_MIGRATING)
- spice_channel_up(channel);
-
- return TRUE;
-}
-
-G_GNUC_INTERNAL
-void spice_channel_up(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- CHANNEL_DEBUG(channel, "channel up, state %d", c->state);
-
- if (SPICE_CHANNEL_GET_CLASS(channel)->channel_up)
- SPICE_CHANNEL_GET_CLASS(channel)->channel_up(channel);
-}
-
-/* coroutine context */
-static void spice_channel_send_link(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- uint8_t *buffer, *p;
- int protocol, i;
-
- c->link_hdr.magic = SPICE_MAGIC;
- c->link_hdr.size = sizeof(c->link_msg);
-
- g_object_get(c->session, "protocol", &protocol, NULL);
- switch (protocol) {
- case 1: /* protocol 1 == major 1, old 0.4 protocol, last active minor */
- c->link_hdr.major_version = 1;
- c->link_hdr.minor_version = 3;
- c->parser = spice_get_server_channel_parser1(c->channel_type, NULL);
- c->marshallers = spice_message_marshallers_get1();
- break;
- case SPICE_VERSION_MAJOR: /* protocol 2 == current */
- c->link_hdr.major_version = SPICE_VERSION_MAJOR;
- c->link_hdr.minor_version = SPICE_VERSION_MINOR;
- c->parser = spice_get_server_channel_parser(c->channel_type, NULL);
- c->marshallers = spice_message_marshallers_get();
- break;
- default:
- g_critical("unknown major %d", protocol);
- return;
- }
-
- c->link_msg.connection_id = spice_session_get_connection_id(c->session);
- c->link_msg.channel_type = c->channel_type;
- c->link_msg.channel_id = c->channel_id;
- c->link_msg.caps_offset = sizeof(c->link_msg);
-
- c->link_msg.num_common_caps = c->common_caps->len;
- c->link_msg.num_channel_caps = c->caps->len;
- c->link_hdr.size += (c->link_msg.num_common_caps +
- c->link_msg.num_channel_caps) * sizeof(uint32_t);
-
- buffer = g_malloc0(sizeof(c->link_hdr) + c->link_hdr.size);
- p = buffer;
-
- memcpy(p, &c->link_hdr, sizeof(c->link_hdr)); p += sizeof(c->link_hdr);
- memcpy(p, &c->link_msg, sizeof(c->link_msg)); p += sizeof(c->link_msg);
-
- for (i = 0; i < c->common_caps->len; i++) {
- *(uint32_t *)p = g_array_index(c->common_caps, uint32_t, i);
- p += sizeof(uint32_t);
- }
- for (i = 0; i < c->caps->len; i++) {
- *(uint32_t *)p = g_array_index(c->caps, uint32_t, i);
- p += sizeof(uint32_t);
- }
- CHANNEL_DEBUG(channel, "channel type %d id %d num common caps %d num caps %d",
- c->link_msg.channel_type,
- c->link_msg.channel_id,
- c->link_msg.num_common_caps,
- c->link_msg.num_channel_caps);
- spice_channel_write(channel, buffer, p - buffer);
- g_free(buffer);
-}
-
-/* coroutine context */
-static gboolean spice_channel_recv_link_hdr(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- int rc;
-
- rc = spice_channel_read(channel, &c->peer_hdr, sizeof(c->peer_hdr));
- if (rc != sizeof(c->peer_hdr)) {
- g_warning("incomplete link header (%d/%" G_GSIZE_FORMAT ")",
- rc, sizeof(c->peer_hdr));
- goto error;
- }
- if (c->peer_hdr.magic != SPICE_MAGIC) {
- g_warning("invalid SPICE_MAGIC!");
- goto error;
- }
-
- CHANNEL_DEBUG(channel, "Peer version: %d:%d", c->peer_hdr.major_version, c->peer_hdr.minor_version);
- if (c->peer_hdr.major_version != c->link_hdr.major_version) {
- g_warning("major mismatch (got %d, expected %d)",
- c->peer_hdr.major_version, c->link_hdr.major_version);
- goto error;
- }
-
- c->peer_msg = g_malloc0(c->peer_hdr.size);
- if (c->peer_msg == NULL) {
- g_warning("invalid peer header size: %u", c->peer_hdr.size);
- goto error;
- }
-
- return TRUE;
-
-error:
- /* Windows socket seems to give early CONNRESET errors. The server
- does not linger when closing the socket if the protocol is
- incompatible. Try with the oldest protocol in this case: */
- if (c->link_hdr.major_version != 1) {
- SPICE_DEBUG("%s: error, switching to protocol 1 (spice 0.4)", c->name);
- c->state = SPICE_CHANNEL_STATE_RECONNECTING;
- g_object_set(c->session, "protocol", 1, NULL);
- return FALSE;
- }
-
- c->event = SPICE_CHANNEL_ERROR_LINK;
- return FALSE;
-}
-
-#if HAVE_SASL
-/*
- * NB, keep in sync with similar method in spice/server/reds.c
- */
-static gchar *addr_to_string(GSocketAddress *addr)
-{
- GInetSocketAddress *iaddr = G_INET_SOCKET_ADDRESS(addr);
- guint16 port;
- GInetAddress *host;
- gchar *hoststr;
- gchar *ret;
-
- host = g_inet_socket_address_get_address(iaddr);
- port = g_inet_socket_address_get_port(iaddr);
- hoststr = g_inet_address_to_string(host);
-
- ret = g_strdup_printf("%s;%hu", hoststr, port);
- g_free(hoststr);
-
- return ret;
-}
-
-static gboolean
-spice_channel_gather_sasl_credentials(SpiceChannel *channel,
- sasl_interact_t *interact)
-{
- SpiceChannelPrivate *c;
- int ninteract;
- gboolean ret = TRUE;
-
- g_return_val_if_fail(channel != NULL, FALSE);
- g_return_val_if_fail(channel->priv != NULL, FALSE);
-
- c = channel->priv;
-
- /* FIXME: we could keep connection open and ask connection details if missing */
-
- for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) {
- switch (interact[ninteract].id) {
- case SASL_CB_AUTHNAME:
- case SASL_CB_USER:
- c->auth_needs_username_and_password = TRUE;
- if (spice_session_get_username(c->session) == NULL)
- return FALSE;
-
- interact[ninteract].result = spice_session_get_username(c->session);
- interact[ninteract].len = strlen(interact[ninteract].result);
- break;
-
- case SASL_CB_PASS:
- if (spice_session_get_password(c->session) == NULL) {
- /* Even if we reach this point, we have to continue looking for
- * SASL_CB_AUTHNAME|SASL_CB_USER, otherwise we would return a
- * wrong error to the applications */
- ret = FALSE;
- continue;
- }
-
- interact[ninteract].result = spice_session_get_password(c->session);
- interact[ninteract].len = strlen(interact[ninteract].result);
- break;
- }
- }
-
- CHANNEL_DEBUG(channel, "Filled SASL interact");
-
- return ret;
-}
-
-/*
- *
- * Init msg from server
- *
- * u32 mechlist-length
- * u8-array mechlist-string
- *
- * Start msg to server
- *
- * u32 mechname-length
- * u8-array mechname-string
- * u32 clientout-length
- * u8-array clientout-string
- *
- * Start msg from server
- *
- * u32 serverin-length
- * u8-array serverin-string
- * u8 continue
- *
- * Step msg to server
- *
- * u32 clientout-length
- * u8-array clientout-string
- *
- * Step msg from server
- *
- * u32 serverin-length
- * u8-array serverin-string
- * u8 continue
- */
-
-#define SASL_MAX_MECHLIST_LEN 300
-#define SASL_MAX_MECHNAME_LEN 100
-#define SASL_MAX_DATA_LEN (1024 * 1024)
-
-/* Perform the SASL authentication process
- */
-static gboolean spice_channel_perform_auth_sasl(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c;
- sasl_conn_t *saslconn = NULL;
- sasl_security_properties_t secprops;
- const char *clientout;
- char *serverin = NULL;
- unsigned int clientoutlen;
- int err;
- char *localAddr = NULL, *remoteAddr = NULL;
- const void *val;
- sasl_ssf_t ssf;
- static const sasl_callback_t saslcb[] = {
- { .id = SASL_CB_USER },
- { .id = SASL_CB_AUTHNAME },
- { .id = SASL_CB_PASS },
- { .id = 0 },
- };
- sasl_interact_t *interact = NULL;
- guint32 len;
- char *mechlist = NULL;
- const char *mechname;
- gboolean ret = FALSE;
- GSocketAddress *addr = NULL;
- guint8 complete;
-
- g_return_val_if_fail(channel != NULL, FALSE);
- g_return_val_if_fail(channel->priv != NULL, FALSE);
-
- c = channel->priv;
-
- /* Sets up the SASL library as a whole */
- err = sasl_client_init(NULL);
- CHANNEL_DEBUG(channel, "Client initialize SASL authentication %d", err);
- if (err != SASL_OK) {
- g_critical("failed to initialize SASL library: %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error;
- }
-
- /* Get local address in form IPADDR:PORT */
- addr = g_socket_get_local_address(c->sock, NULL);
- if (!addr) {
- g_critical("failed to get local address");
- goto error;
- }
- if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
- g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
- (localAddr = addr_to_string(addr)) == NULL)
- goto error;
- g_clear_object(&addr);
-
- /* Get remote address in form IPADDR:PORT */
- addr = g_socket_get_remote_address(c->sock, NULL);
- if (!addr) {
- g_critical("failed to get peer address");
- goto error;
- }
- if ((g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV4 ||
- g_socket_address_get_family(addr) == G_SOCKET_FAMILY_IPV6) &&
- (remoteAddr = addr_to_string(addr)) == NULL)
- goto error;
- g_clear_object(&addr);
-
- CHANNEL_DEBUG(channel, "Client SASL new host:'%s' local:'%s' remote:'%s'",
- spice_session_get_host(c->session), localAddr, remoteAddr);
-
- /* Setup a handle for being a client */
- err = sasl_client_new("spice",
- spice_session_get_host(c->session),
- localAddr,
- remoteAddr,
- saslcb,
- SASL_SUCCESS_DATA,
- &saslconn);
-
- if (err != SASL_OK) {
- g_critical("Failed to create SASL client context: %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error;
- }
-
- if (c->ssl) {
- sasl_ssf_t ssf;
-
- ssf = SSL_get_cipher_bits(c->ssl, NULL);
- err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
- if (err != SASL_OK) {
- g_critical("cannot set SASL external SSF %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error;
- }
- }
-
- memset(&secprops, 0, sizeof secprops);
- /* If we've got TLS, we don't care about SSF */
- secprops.min_ssf = c->ssl ? 0 : 56; /* Equiv to DES supported by all Kerberos */
- secprops.max_ssf = c->ssl ? 0 : 100000; /* Very strong ! AES == 256 */
- secprops.maxbufsize = 100000;
- /* If we're not TLS, then forbid any anonymous or trivially crackable auth */
- secprops.security_flags = c->ssl ? 0 :
- SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
-
- err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
- if (err != SASL_OK) {
- g_critical("cannot set security props %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error;
- }
-
- /* Get the supported mechanisms from the server */
- spice_channel_read(channel, &len, sizeof(len));
- if (c->has_error)
- goto error;
- if (len > SASL_MAX_MECHLIST_LEN) {
- g_critical("mechlistlen %d too long", len);
- goto error;
- }
-
- mechlist = g_malloc0(len + 1);
- spice_channel_read(channel, mechlist, len);
- mechlist[len] = '\0';
- if (c->has_error) {
- goto error;
- }
-
-restart:
- /* Start the auth negotiation on the client end first */
- CHANNEL_DEBUG(channel, "Client start negotiation mechlist '%s'", mechlist);
- err = sasl_client_start(saslconn,
- mechlist,
- &interact,
- &clientout,
- &clientoutlen,
- &mechname);
- if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
- g_critical("Failed to start SASL negotiation: %d (%s)",
- err, sasl_errdetail(saslconn));
- goto error;
- }
-
- /* Need to gather some credentials from the client */
- if (err == SASL_INTERACT) {
- if (!spice_channel_gather_sasl_credentials(channel, interact)) {
- CHANNEL_DEBUG(channel, "Failed to collect auth credentials");
- goto error;
- }
- goto restart;
- }
-
- CHANNEL_DEBUG(channel, "Server start negotiation with mech %s. Data %d bytes %p '%s'",
- mechname, clientoutlen, clientout, clientout);
-
- if (clientoutlen > SASL_MAX_DATA_LEN) {
- g_critical("SASL negotiation data too long: %d bytes",
- clientoutlen);
- goto error;
- }
-
- /* Send back the chosen mechname */
- len = strlen(mechname);
- spice_channel_write(channel, &len, sizeof(guint32));
- spice_channel_write(channel, mechname, len);
-
- /* NB, distinction of NULL vs "" is *critical* in SASL */
- if (clientout) {
- len = clientoutlen + 1;
- spice_channel_write(channel, &len, sizeof(guint32));
- spice_channel_write(channel, clientout, len);
- } else {
- len = 0;
- spice_channel_write(channel, &len, sizeof(guint32));
- }
-
- if (c->has_error)
- goto error;
-
- CHANNEL_DEBUG(channel, "Getting sever start negotiation reply");
- /* Read the 'START' message reply from server */
- spice_channel_read(channel, &len, sizeof(len));
- if (c->has_error)
- goto error;
- if (len > SASL_MAX_DATA_LEN) {
- g_critical("SASL negotiation data too long: %d bytes",
- len);
- goto error;
- }
-
- /* NB, distinction of NULL vs "" is *critical* in SASL */
- if (len > 0) {
- serverin = g_malloc0(len);
- spice_channel_read(channel, serverin, len);
- serverin[len - 1] = '\0';
- len--;
- } else {
- serverin = NULL;
- }
- spice_channel_read(channel, &complete, sizeof(guint8));
- if (c->has_error)
- goto error;
-
- CHANNEL_DEBUG(channel, "Client start result complete: %d. Data %d bytes %p '%s'",
- complete, len, serverin, serverin);
-
- /* Loop-the-loop...
- * Even if the server has completed, the client must *always* do at least one step
- * in this loop to verify the server isn't lying about something. Mutual auth */
- for (;;) {
- if (complete && err == SASL_OK)
- break;
-
- restep:
- err = sasl_client_step(saslconn,
- serverin,
- len,
- &interact,
- &clientout,
- &clientoutlen);
- if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
- g_critical("Failed SASL step: %d (%s)",
- err, sasl_errdetail(saslconn));
- goto error;
- }
-
- /* Need to gather some credentials from the client */
- if (err == SASL_INTERACT) {
- if (!spice_channel_gather_sasl_credentials(channel,
- interact)) {
- CHANNEL_DEBUG(channel, "%s", "Failed to collect auth credentials");
- goto error;
- }
- goto restep;
- }
-
- if (serverin) {
- g_free(serverin);
- serverin = NULL;
- }
-
- CHANNEL_DEBUG(channel, "Client step result %d. Data %d bytes %p '%s'", err, clientoutlen, clientout, clientout);
-
- /* Previous server call showed completion & we're now locally complete too */
- if (complete && err == SASL_OK)
- break;
-
- /* Not done, prepare to talk with the server for another iteration */
-
- /* NB, distinction of NULL vs "" is *critical* in SASL */
- if (clientout) {
- len = clientoutlen + 1;
- spice_channel_write(channel, &len, sizeof(guint32));
- spice_channel_write(channel, clientout, len);
- } else {
- len = 0;
- spice_channel_write(channel, &len, sizeof(guint32));
- }
-
- if (c->has_error)
- goto error;
-
- CHANNEL_DEBUG(channel, "Server step with %d bytes %p", clientoutlen, clientout);
-
- spice_channel_read(channel, &len, sizeof(guint32));
- if (c->has_error)
- goto error;
- if (len > SASL_MAX_DATA_LEN) {
- g_critical("SASL negotiation data too long: %d bytes", len);
- goto error;
- }
-
- /* NB, distinction of NULL vs "" is *critical* in SASL */
- if (len) {
- serverin = g_malloc0(len);
- spice_channel_read(channel, serverin, len);
- serverin[len - 1] = '\0';
- len--;
- } else {
- serverin = NULL;
- }
-
- spice_channel_read(channel, &complete, sizeof(guint8));
- if (c->has_error)
- goto error;
-
- CHANNEL_DEBUG(channel, "Client step result complete: %d. Data %d bytes %p '%s'",
- complete, len, serverin, serverin);
-
- /* This server call shows complete, and earlier client step was OK */
- if (complete) {
- g_free(serverin);
- serverin = NULL;
- if (err == SASL_CONTINUE) /* something went wrong */
- goto complete;
- break;
- }
- }
-
- /* Check for suitable SSF if non-TLS */
- if (!c->ssl) {
- err = sasl_getprop(saslconn, SASL_SSF, &val);
- if (err != SASL_OK) {
- g_critical("cannot query SASL ssf on connection %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
- goto error;
- }
- ssf = *(const int *)val;
- CHANNEL_DEBUG(channel, "SASL SSF value %d", ssf);
- if (ssf < 56) { /* 56 == DES level, good for Kerberos */
- g_critical("negotiation SSF %d was not strong enough", ssf);
- goto error;
- }
- }
-
-complete:
- CHANNEL_DEBUG(channel, "%s", "SASL authentication complete");
- spice_channel_read(channel, &len, sizeof(len));
- if (len == SPICE_LINK_ERR_OK) {
- ret = TRUE;
- /* This must come *after* check-auth-result, because the former
- * is defined to be sent unencrypted, and setting saslconn turns
- * on the SSF layer encryption processing */
- c->sasl_conn = saslconn;
- goto cleanup;
- }
-
-error:
- if (saslconn)
- sasl_dispose(&saslconn);
-
- spice_channel_failed_authentication(channel);
- ret = FALSE;
-
-cleanup:
- g_free(localAddr);
- g_free(remoteAddr);
- g_free(mechlist);
- g_free(serverin);
- g_clear_object(&addr);
- return ret;
-}
-#endif /* HAVE_SASL */
-
-/* coroutine context */
-static gboolean spice_channel_recv_link_msg(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c;
- int rc, num_caps, i;
- uint32_t *caps;
-
- g_return_val_if_fail(channel != NULL, FALSE);
- g_return_val_if_fail(channel->priv != NULL, FALSE);
-
- c = channel->priv;
-
- rc = spice_channel_read(channel, (uint8_t*)c->peer_msg + c->peer_pos,
- c->peer_hdr.size - c->peer_pos);
- c->peer_pos += rc;
- if (c->peer_pos != c->peer_hdr.size) {
- g_critical("%s: %s: incomplete link reply (%d/%d)",
- c->name, __FUNCTION__, rc, c->peer_hdr.size);
- goto error;
- }
- switch (c->peer_msg->error) {
- case SPICE_LINK_ERR_OK:
- /* nothing */
- break;
- case SPICE_LINK_ERR_NEED_SECURED:
- c->state = SPICE_CHANNEL_STATE_RECONNECTING;
- CHANNEL_DEBUG(channel, "switching to tls");
- c->tls = TRUE;
- return FALSE;
- default:
- g_warning("%s: %s: unhandled error %d",
- c->name, __FUNCTION__, c->peer_msg->error);
- goto error;
- }
-
- num_caps = c->peer_msg->num_channel_caps + c->peer_msg->num_common_caps;
- CHANNEL_DEBUG(channel, "%s: %d caps", __FUNCTION__, num_caps);
-
- /* see original spice/client code: */
- /* g_return_if_fail(c->peer_msg + c->peer_msg->caps_offset * sizeof(uint32_t) > c->peer_msg + c->peer_hdr.size); */
-
- caps = (uint32_t *)((uint8_t *)c->peer_msg + c->peer_msg->caps_offset);
-
- g_array_set_size(c->remote_common_caps, c->peer_msg->num_common_caps);
- for (i = 0; i < c->peer_msg->num_common_caps; i++, caps++) {
- g_array_index(c->remote_common_caps, uint32_t, i) = *caps;
- CHANNEL_DEBUG(channel, "got common caps %u:0x%X", i, *caps);
- }
-
- g_array_set_size(c->remote_caps, c->peer_msg->num_channel_caps);
- for (i = 0; i < c->peer_msg->num_channel_caps; i++, caps++) {
- g_array_index(c->remote_caps, uint32_t, i) = *caps;
- CHANNEL_DEBUG(channel, "got channel caps %u:0x%X", i, *caps);
- }
-
- if (!spice_channel_test_common_capability(channel,
- SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION)) {
- CHANNEL_DEBUG(channel, "Server supports spice ticket auth only");
- spice_channel_send_spice_ticket(channel);
- } else {
- SpiceLinkAuthMechanism auth = { 0, };
-
-#if HAVE_SASL
- if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SASL)) {
- CHANNEL_DEBUG(channel, "Choosing SASL mechanism");
- auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SASL;
- spice_channel_write(channel, &auth, sizeof(auth));
- if (!spice_channel_perform_auth_sasl(channel))
- return FALSE;
- } else
-#endif
- if (spice_channel_test_common_capability(channel, SPICE_COMMON_CAP_AUTH_SPICE)) {
- auth.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
- spice_channel_write(channel, &auth, sizeof(auth));
- spice_channel_send_spice_ticket(channel);
- } else {
- g_warning("No compatible AUTH mechanism");
- goto error;
- }
- }
- c->use_mini_header = spice_channel_test_common_capability(channel,
- SPICE_COMMON_CAP_MINI_HEADER);
- CHANNEL_DEBUG(channel, "use mini header: %d", c->use_mini_header);
- return TRUE;
-
-error:
- c->has_error = TRUE;
- c->event = SPICE_CHANNEL_ERROR_LINK;
- return FALSE;
-}
-
-/* system context */
-G_GNUC_INTERNAL
-void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel)
-{
- GCoroutine *c = &channel->priv->coroutine;
-
- if (cancel)
- g_coroutine_condition_cancel(c);
-
- g_coroutine_wakeup(c);
-}
-
-G_GNUC_INTERNAL
-gboolean spice_channel_get_read_only(SpiceChannel *channel)
-{
- return spice_session_get_read_only(channel->priv->session);
-}
-
-/* coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_recv_msg(SpiceChannel *channel,
- handler_msg_in msg_handler, gpointer data)
-{
- SpiceChannelPrivate *c = channel->priv;
- SpiceMsgIn *in;
- int msg_size;
- int msg_type;
- int sub_list_offset = 0;
-
- in = spice_msg_in_new(channel);
-
- /* receive message */
- spice_channel_read(channel, in->header,
- spice_header_get_header_size(c->use_mini_header));
- if (c->has_error)
- goto end;
-
- msg_size = spice_header_get_msg_size(in->header, c->use_mini_header);
- /* FIXME: do not allow others to take ref on in, and use realloc here?
- * this would avoid malloc/free on each message?
- */
- in->data = g_malloc0(msg_size);
- spice_channel_read(channel, in->data, msg_size);
- if (c->has_error)
- goto end;
- in->dpos = msg_size;
-
- msg_type = spice_header_get_msg_type(in->header, c->use_mini_header);
- sub_list_offset = spice_header_get_msg_sub_list(in->header, c->use_mini_header);
-
- if (msg_type == SPICE_MSG_LIST || sub_list_offset) {
- SpiceSubMessageList *sub_list;
- SpiceSubMessage *sub;
- SpiceMsgIn *sub_in;
- int i;
-
- sub_list = (SpiceSubMessageList *)(in->data + sub_list_offset);
- for (i = 0; i < sub_list->size; i++) {
- sub = (SpiceSubMessage *)(in->data + sub_list->sub_messages[i]);
- sub_in = spice_msg_in_sub_new(channel, in, sub);
- sub_in->parsed = c->parser(sub_in->data, sub_in->data + sub_in->dpos,
- spice_header_get_msg_type(sub_in->header,
- c->use_mini_header),
- c->peer_hdr.minor_version,
- &sub_in->psize, &sub_in->pfree);
- if (sub_in->parsed == NULL) {
- g_critical("failed to parse sub-message: %s type %d",
- c->name, spice_header_get_msg_type(sub_in->header, c->use_mini_header));
- goto end;
- }
- msg_handler(channel, sub_in, data);
- spice_msg_in_unref(sub_in);
- }
- }
-
- /* ack message */
- if (c->message_ack_count) {
- c->message_ack_count--;
- if (!c->message_ack_count) {
- SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK);
- spice_msg_out_send_internal(out);
- c->message_ack_count = c->message_ack_window;
- }
- }
-
- if (msg_type == SPICE_MSG_LIST) {
- goto end;
- }
-
- /* parse message */
- in->parsed = c->parser(in->data, in->data + msg_size, msg_type,
- c->peer_hdr.minor_version, &in->psize, &in->pfree);
- if (in->parsed == NULL) {
- g_critical("failed to parse message: %s type %d",
- c->name, msg_type);
- goto end;
- }
-
- /* process message */
- /* spice_msg_in_hexdump(in); */
- msg_handler(channel, in, data);
-
-end:
- /* If the server uses full header, the serial is not necessarily equal
- * to c->in_serial (the server can sometimes skip serials) */
- c->last_message_serial = spice_header_get_in_msg_serial(in);
- c->in_serial++;
- spice_msg_in_unref(in);
-}
-
-static const char *to_string[] = {
- NULL,
- [ SPICE_CHANNEL_MAIN ] = "main",
- [ SPICE_CHANNEL_DISPLAY ] = "display",
- [ SPICE_CHANNEL_INPUTS ] = "inputs",
- [ SPICE_CHANNEL_CURSOR ] = "cursor",
- [ SPICE_CHANNEL_PLAYBACK ] = "playback",
- [ SPICE_CHANNEL_RECORD ] = "record",
- [ SPICE_CHANNEL_TUNNEL ] = "tunnel",
- [ SPICE_CHANNEL_SMARTCARD ] = "smartcard",
- [ SPICE_CHANNEL_USBREDIR ] = "usbredir",
- [ SPICE_CHANNEL_PORT ] = "port",
- [ SPICE_CHANNEL_WEBDAV ] = "webdav",
-};
-
-/**
- * spice_channel_type_to_string:
- * @type: a channel-type property value
- *
- * Convert a channel-type property value to a string.
- *
- * Returns: string representation of @type.
- * Since: 0.20
- **/
-const gchar* spice_channel_type_to_string(gint type)
-{
- const char *str = NULL;
-
- if (type >= 0 && type < G_N_ELEMENTS(to_string)) {
- str = to_string[type];
- }
-
- return str ? str : "unknown channel type";
-}
-
-/**
- * spice_channel_string_to_type:
- * @str: a string representation of the channel-type property
- *
- * Convert a channel-type property value to a string.
- *
- * Returns: the channel-type property value for a @str channel
- * Since: 0.21
- **/
-gint spice_channel_string_to_type(const gchar *str)
-{
- int i;
-
- g_return_val_if_fail(str != NULL, -1);
-
- for (i = 0; i < G_N_ELEMENTS(to_string); i++)
- if (g_strcmp0(str, to_string[i]) == 0)
- return i;
-
- return -1;
-}
-
-G_GNUC_INTERNAL
-gchar *spice_channel_supported_string(void)
-{
- return g_strjoin(", ",
- spice_channel_type_to_string(SPICE_CHANNEL_MAIN),
- spice_channel_type_to_string(SPICE_CHANNEL_DISPLAY),
- spice_channel_type_to_string(SPICE_CHANNEL_INPUTS),
- spice_channel_type_to_string(SPICE_CHANNEL_CURSOR),
- spice_channel_type_to_string(SPICE_CHANNEL_PLAYBACK),
- spice_channel_type_to_string(SPICE_CHANNEL_RECORD),
-#ifdef USE_SMARTCARD
- spice_channel_type_to_string(SPICE_CHANNEL_SMARTCARD),
-#endif
-#ifdef USE_USBREDIR
- spice_channel_type_to_string(SPICE_CHANNEL_USBREDIR),
-#endif
-#ifdef USE_PHODAV
- spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
-#endif
- NULL);
-}
-
-
-/**
- * spice_channel_new:
- * @s: the @SpiceSession the channel is linked to
- * @type: the requested SPICECHANNELPRIVATE type
- * @id: the channel-id
- *
- * Create a new #SpiceChannel of type @type, and channel ID @id.
- *
- * Returns: a weak reference to #SpiceChannel, the session owns the reference
- **/
-SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
-{
- SpiceChannel *channel;
- GType gtype = 0;
-
- g_return_val_if_fail(s != NULL, NULL);
-
- switch (type) {
- case SPICE_CHANNEL_MAIN:
- gtype = SPICE_TYPE_MAIN_CHANNEL;
- break;
- case SPICE_CHANNEL_DISPLAY:
- gtype = SPICE_TYPE_DISPLAY_CHANNEL;
- break;
- case SPICE_CHANNEL_CURSOR:
- gtype = SPICE_TYPE_CURSOR_CHANNEL;
- break;
- case SPICE_CHANNEL_INPUTS:
- gtype = SPICE_TYPE_INPUTS_CHANNEL;
- break;
- case SPICE_CHANNEL_PLAYBACK:
- case SPICE_CHANNEL_RECORD: {
- if (!spice_session_get_audio_enabled(s)) {
- g_debug("audio channel is disabled, not creating it");
- return NULL;
- }
- gtype = type == SPICE_CHANNEL_RECORD ?
- SPICE_TYPE_RECORD_CHANNEL : SPICE_TYPE_PLAYBACK_CHANNEL;
- break;
- }
-#ifdef USE_SMARTCARD
- case SPICE_CHANNEL_SMARTCARD: {
- if (!spice_session_get_smartcard_enabled(s)) {
- g_debug("smartcard channel is disabled, not creating it");
- return NULL;
- }
- gtype = SPICE_TYPE_SMARTCARD_CHANNEL;
- break;
- }
-#endif
-#ifdef USE_USBREDIR
- case SPICE_CHANNEL_USBREDIR: {
- if (!spice_session_get_usbredir_enabled(s)) {
- g_debug("usbredir channel is disabled, not creating it");
- return NULL;
- }
- gtype = SPICE_TYPE_USBREDIR_CHANNEL;
- break;
- }
-#endif
-#ifdef USE_PHODAV
- case SPICE_CHANNEL_WEBDAV: {
- gtype = SPICE_TYPE_WEBDAV_CHANNEL;
- break;
- }
-#endif
- case SPICE_CHANNEL_PORT:
- gtype = SPICE_TYPE_PORT_CHANNEL;
- break;
- default:
- g_debug("unsupported channel kind: %s: %d",
- spice_channel_type_to_string(type), type);
- return NULL;
- }
- channel = SPICE_CHANNEL(g_object_new(gtype,
- "spice-session", s,
- "channel-type", type,
- "channel-id", id,
- NULL));
- return channel;
-}
-
-/**
- * spice_channel_destroy:
- * @channel:
- *
- * Disconnect and unref the @channel.
- *
- * Deprecated: 0.27: this function has been deprecated because it is
- * misleading, the object is not actually destroyed. Instead, it is
- * recommended to call explicitely spice_channel_disconnect() and
- * g_object_unref().
- **/
-void spice_channel_destroy(SpiceChannel *channel)
-{
- g_return_if_fail(channel != NULL);
-
- CHANNEL_DEBUG(channel, "channel destroy");
- spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
- g_object_unref(channel);
-}
-
-/* any context */
-static void spice_channel_flushed(SpiceChannel *channel, gboolean success)
-{
- SpiceChannelPrivate *c = channel->priv;
- GSList *l;
-
- for (l = c->flushing; l != NULL; l = l->next) {
- GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
- g_simple_async_result_set_op_res_gboolean(result, success);
- g_simple_async_result_complete_in_idle(result);
- }
-
- g_slist_free_full(c->flushing, g_object_unref);
- c->flushing = NULL;
-}
-
-/* coroutine context */
-static void spice_channel_iterate_write(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- SpiceMsgOut *out;
-
- do {
- STATIC_MUTEX_LOCK(c->xmit_queue_lock);
- out = g_queue_pop_head(&c->xmit_queue);
- STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
- if (out)
- spice_channel_write_msg(channel, out);
- } while (out);
-
- spice_channel_flushed(channel, TRUE);
-}
-
-/* coroutine context */
-static void spice_channel_iterate_read(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);
-
- /* treat all incoming data (block on message completion) */
- while (!c->has_error &&
- c->state != SPICE_CHANNEL_STATE_MIGRATING &&
- g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(c->in))
- ) { do
- spice_channel_recv_msg(channel,
- (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
-#if HAVE_SASL
- /* flush the sasl buffer too */
- while (c->sasl_decoded != NULL);
-#else
- while (FALSE);
-#endif
- }
-
-}
-
-static gboolean wait_migration(gpointer data)
-{
- SpiceChannel *channel = SPICE_CHANNEL(data);
- SpiceChannelPrivate *c = channel->priv;
-
- if (c->state != SPICE_CHANNEL_STATE_MIGRATING) {
- CHANNEL_DEBUG(channel, "unfreeze channel");
- return TRUE;
- }
-
- return FALSE;
-}
-
-/* coroutine context */
-static gboolean spice_channel_iterate(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- if (c->state == SPICE_CHANNEL_STATE_MIGRATING &&
- !g_coroutine_condition_wait(&c->coroutine, wait_migration, channel))
- CHANNEL_DEBUG(channel, "migration wait cancelled");
-
- /* flush any pending write and read */
- if (!c->has_error)
- SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
- if (!c->has_error)
- SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel);
-
- if (c->has_error) {
- GIOCondition ret;
-
- if (!c->sock)
- return FALSE;
-
- /* We don't want to report an error if the socket was closed gracefully
- * on the other end (VM shutdown) */
- ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR);
-
- if (ret & G_IO_ERR) {
- CHANNEL_DEBUG(channel, "channel got error");
-
- if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
- if (c->state == SPICE_CHANNEL_STATE_READY)
- c->event = SPICE_CHANNEL_ERROR_IO;
- else
- c->event = SPICE_CHANNEL_ERROR_LINK;
- }
- }
- return FALSE;
- }
-
- return TRUE;
-}
-
-/* we use an idle function to allow the coroutine to exit before we actually
- * unref the object since the coroutine's state is part of the object */
-static gboolean spice_channel_delayed_unref(gpointer data)
-{
- SpiceChannel *channel = SPICE_CHANNEL(data);
- SpiceChannelPrivate *c = channel->priv;
- gboolean was_ready = c->state == SPICE_CHANNEL_STATE_READY;
-
- CHANNEL_DEBUG(channel, "Delayed unref channel %p", channel);
-
- g_return_val_if_fail(c->coroutine.coroutine.exited == TRUE, FALSE);
-
- c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
-
- if (c->event != SPICE_CHANNEL_NONE) {
- g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, c->event);
- c->event = SPICE_CHANNEL_NONE;
- g_clear_error(&c->error);
- }
-
- if (was_ready)
- g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_CLOSED);
-
- g_object_unref(G_OBJECT(data));
-
- return FALSE;
-}
-
-static X509_LOOKUP_METHOD spice_x509_mem_lookup = {
- "spice_x509_mem_lookup",
- 0
-};
-
-static int spice_channel_load_ca(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- STACK_OF(X509_INFO) *inf;
- X509_INFO *itmp;
- X509_LOOKUP *lookup;
- BIO *in;
- int i, count = 0;
- guint8 *ca;
- guint size;
- const gchar *ca_file;
- int rc;
-
- g_return_val_if_fail(c->ctx != NULL, 0);
-
- lookup = X509_STORE_add_lookup(c->ctx->cert_store, &spice_x509_mem_lookup);
- ca_file = spice_session_get_ca_file(c->session);
- spice_session_get_ca(c->session, &ca, &size);
-
- CHANNEL_DEBUG(channel, "Load CA, file: %s, data: %p", ca_file, ca);
- g_warn_if_fail(ca_file || ca);
-
- if (ca != NULL) {
- in = BIO_new_mem_buf(ca, size);
- inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
- BIO_free(in);
-
- for (i = 0; i < sk_X509_INFO_num(inf); i++) {
- itmp = sk_X509_INFO_value(inf, i);
- if (itmp->x509) {
- X509_STORE_add_cert(lookup->store_ctx, itmp->x509);
- count++;
- }
- if (itmp->crl) {
- X509_STORE_add_crl(lookup->store_ctx, itmp->crl);
- count++;
- }
- }
-
- sk_X509_INFO_pop_free(inf, X509_INFO_free);
- }
-
- if (ca_file != NULL) {
- rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
- if (rc != 1)
- g_warning("loading ca certs from %s failed", ca_file);
- else
- count++;
- }
-
- if (count == 0) {
- rc = SSL_CTX_set_default_verify_paths(c->ctx);
- if (rc != 1)
- g_warning("loading ca certs from default location failed");
- else
- count++;
- }
-
- return count;
-}
-
-/**
- * spice_channel_get_error:
- * @channel:
- *
- * Retrieves the #GError currently set on channel, if the #SpiceChannel
- * is in error state and can provide additional error details.
- *
- * Returns: the pointer to the error, or %NULL
- * Since: 0.24
- **/
-const GError* spice_channel_get_error(SpiceChannel *self)
-{
- SpiceChannelPrivate *c;
-
- g_return_val_if_fail(SPICE_IS_CHANNEL(self), NULL);
- c = self->priv;
-
- return c->error;
-}
-
-/* coroutine context */
-static void *spice_channel_coroutine(void *data)
-{
- SpiceChannel *channel = SPICE_CHANNEL(data);
- SpiceChannelPrivate *c = channel->priv;
- guint verify;
- int rc, delay_val = 1;
- /* When some other SSL/TLS version becomes obsolete, add it to this
- * variable. */
- long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-
- CHANNEL_DEBUG(channel, "Started background coroutine %p", &c->coroutine);
-
- if (spice_session_get_client_provided_socket(c->session)) {
- if (c->fd < 0) {
- g_critical("fd not provided!");
- c->event = SPICE_CHANNEL_ERROR_CONNECT;
- goto cleanup;
- }
-
- if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) {
- CHANNEL_DEBUG(channel, "Failed to open socket from fd %d", c->fd);
- c->event = SPICE_CHANNEL_ERROR_CONNECT;
- goto cleanup;
- }
-
- g_socket_set_blocking(c->sock, FALSE);
- g_socket_set_keepalive(c->sock, TRUE);
- c->conn = g_socket_connection_factory_create_connection(c->sock);
- goto connected;
- }
-
-
-reconnect:
- c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);
- if (c->conn == NULL) {
- if (!c->error && !c->tls) {
- CHANNEL_DEBUG(channel, "trying with TLS port");
- c->tls = true; /* FIXME: does that really work with provided fd */
- goto reconnect;
- } else {
- CHANNEL_DEBUG(channel, "Connect error");
- c->event = SPICE_CHANNEL_ERROR_CONNECT;
- goto cleanup;
- }
- }
- c->sock = g_object_ref(g_socket_connection_get_socket(c->conn));
-
- if (c->tls) {
- c->ctx = SSL_CTX_new(SSLv23_method());
- if (c->ctx == NULL) {
- g_critical("SSL_CTX_new failed");
- c->event = SPICE_CHANNEL_ERROR_TLS;
- goto cleanup;
- }
-
- SSL_CTX_set_options(c->ctx, ssl_options);
-
- verify = spice_session_get_verify(c->session);
- if (verify &
- (SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
- rc = spice_channel_load_ca(channel);
- if (rc == 0) {
- g_warning("no cert loaded");
- if (verify & SPICE_SESSION_VERIFY_PUBKEY) {
- g_warning("only pubkey active");
- verify = SPICE_SESSION_VERIFY_PUBKEY;
- } else {
- c->event = SPICE_CHANNEL_ERROR_TLS;
- goto cleanup;
- }
- }
- }
-
- {
- const gchar *ciphers = spice_session_get_ciphers(c->session);
- if (ciphers != NULL) {
- rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);
- if (rc != 1)
- g_warning("loading cipher list %s failed", ciphers);
- }
- }
-
- c->ssl = SSL_new(c->ctx);
- if (c->ssl == NULL) {
- g_critical("SSL_new failed");
- c->event = SPICE_CHANNEL_ERROR_TLS;
- goto cleanup;
- }
-
-
- BIO *bio = bio_new_giostream(G_IO_STREAM(c->conn));
- SSL_set_bio(c->ssl, bio, bio);
-
- {
- guint8 *pubkey;
- guint pubkey_len;
-
- spice_session_get_pubkey(c->session, &pubkey, &pubkey_len);
- c->sslverify = spice_openssl_verify_new(c->ssl, verify,
- spice_session_get_host(c->session),
- (char*)pubkey, pubkey_len,
- spice_session_get_cert_subject(c->session));
- }
-
-ssl_reconnect:
- rc = SSL_connect(c->ssl);
- if (rc <= 0) {
- rc = SSL_get_error(c->ssl, rc);
- if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) {
- g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_OUT|G_IO_ERR|G_IO_HUP);
- goto ssl_reconnect;
- } else {
- g_warning("%s: SSL_connect: %s",
- c->name, ERR_error_string(rc, NULL));
- c->event = SPICE_CHANNEL_ERROR_TLS;
- goto cleanup;
- }
- }
- }
-
-connected:
- c->has_error = FALSE;
- c->in = g_io_stream_get_input_stream(G_IO_STREAM(c->conn));
- c->out = g_io_stream_get_output_stream(G_IO_STREAM(c->conn));
-
- rc = setsockopt(g_socket_get_fd(c->sock), IPPROTO_TCP, TCP_NODELAY,
- (const char*)&delay_val, sizeof(delay_val));
- if ((rc != 0)
-#ifdef ENOTSUP
- && (errno != ENOTSUP)
-#endif
- ) {
- g_warning("%s: could not set sockopt TCP_NODELAY: %s", c->name,
- strerror(errno));
- }
-
- spice_channel_send_link(channel);
- if (!spice_channel_recv_link_hdr(channel) ||
- !spice_channel_recv_link_msg(channel) ||
- !spice_channel_recv_auth(channel))
- goto cleanup;
-
- while (spice_channel_iterate(channel))
- ;
-
-cleanup:
- CHANNEL_DEBUG(channel, "Coroutine exit %s", c->name);
-
- spice_channel_reset(channel, FALSE);
-
- if (c->state == SPICE_CHANNEL_STATE_RECONNECTING ||
- c->state == SPICE_CHANNEL_STATE_SWITCHING) {
- g_warn_if_fail(c->event == SPICE_CHANNEL_NONE);
- channel_connect(channel, c->tls);
- g_object_unref(channel);
- } else
- g_idle_add(spice_channel_delayed_unref, data);
-
- /* Co-routine exits now - the SpiceChannel object may no longer exist,
- so don't do anything else now unless you like SEGVs */
- return NULL;
-}
-
-static gboolean connect_delayed(gpointer data)
-{
- SpiceChannel *channel = data;
- SpiceChannelPrivate *c = channel->priv;
- struct coroutine *co;
-
- CHANNEL_DEBUG(channel, "Open coroutine starting %p", channel);
- c->connect_delayed_id = 0;
-
- co = &c->coroutine.coroutine;
-
- co->stack_size = 16 << 20; /* 16Mb */
- co->entry = spice_channel_coroutine;
- co->release = NULL;
-
- coroutine_init(co);
- coroutine_yieldto(co, channel);
-
- return FALSE;
-}
-
-/* any context */
-static gboolean channel_connect(SpiceChannel *channel, gboolean tls)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- g_return_val_if_fail(c != NULL, FALSE);
-
- if (c->session == NULL || c->channel_type == -1 || c->channel_id == -1) {
- /* unset properties or unknown channel type */
- g_warning("%s: channel setup incomplete", __FUNCTION__);
- return false;
- }
-
- c->state = SPICE_CHANNEL_STATE_CONNECTING;
- c->tls = tls;
-
- if (spice_session_get_client_provided_socket(c->session)) {
- if (c->fd == -1) {
- CHANNEL_DEBUG(channel, "requesting fd");
- /* FIXME: no way for client to provide fd atm. */
- /* It could either chain on parent channel.. */
- /* or register migration channel on parent session, or ? */
- g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
- return true;
- }
- }
-
- c->xmit_queue_blocked = FALSE;
-
- g_return_val_if_fail(c->sock == NULL, FALSE);
- g_object_ref(G_OBJECT(channel)); /* Unref'd when co-routine exits */
-
- /* we connect in idle, to let previous coroutine exit, if present */
- c->connect_delayed_id = g_idle_add(connect_delayed, channel);
-
- return true;
-}
-
-/**
- * spice_channel_connect:
- * @channel:
- *
- * Connect the channel, using #SpiceSession connection informations
- *
- * Returns: %TRUE on success.
- **/
-gboolean spice_channel_connect(SpiceChannel *channel)
-{
- g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
- SpiceChannelPrivate *c = channel->priv;
-
- if (c->state >= SPICE_CHANNEL_STATE_CONNECTING)
- return TRUE;
-
- g_return_val_if_fail(channel->priv->fd == -1, FALSE);
-
- return channel_connect(channel, FALSE);
-}
-
-/**
- * spice_channel_open_fd:
- * @channel:
- * @fd: a file descriptor (socket) or -1.
- * request mechanism
- *
- * Connect the channel using @fd socket.
- *
- * If @fd is -1, a valid fd will be requested later via the
- * SpiceChannel::open-fd signal.
- *
- * Returns: %TRUE on success.
- **/
-gboolean spice_channel_open_fd(SpiceChannel *channel, int fd)
-{
- SpiceChannelPrivate *c;
-
- g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
- g_return_val_if_fail(channel->priv != NULL, FALSE);
- g_return_val_if_fail(channel->priv->fd == -1, FALSE);
- g_return_val_if_fail(fd >= -1, FALSE);
-
- c = channel->priv;
- if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {
- g_warning("Invalid channel_connect state: %d", c->state);
- return true;
- }
-
- c->fd = fd;
-
- return channel_connect(channel, FALSE);
-}
-
-/* system or coroutine context */
-static void channel_reset(SpiceChannel *channel, gboolean migrating)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- CHANNEL_DEBUG(channel, "channel reset");
- if (c->connect_delayed_id) {
- g_source_remove(c->connect_delayed_id);
- c->connect_delayed_id = 0;
- }
-
-#if HAVE_SASL
- if (c->sasl_conn) {
- sasl_dispose(&c->sasl_conn);
- c->sasl_conn = NULL;
- c->sasl_decoded_offset = c->sasl_decoded_length = 0;
- }
-#endif
-
- spice_openssl_verify_free(c->sslverify);
- c->sslverify = NULL;
-
- if (c->ssl) {
- SSL_free(c->ssl);
- c->ssl = NULL;
- }
-
- if (c->ctx) {
- SSL_CTX_free(c->ctx);
- c->ctx = NULL;
- }
-
- if (c->conn) {
- g_object_unref(c->conn);
- c->conn = NULL;
- }
-
- g_clear_object(&c->sock);
-
- c->fd = -1;
-
- c->auth_needs_username_and_password = FALSE;
-
- g_free(c->peer_msg);
- c->peer_msg = NULL;
- c->peer_pos = 0;
-
- STATIC_MUTEX_LOCK(c->xmit_queue_lock);
- c->xmit_queue_blocked = TRUE; /* Disallow queuing new messages */
- gboolean was_empty = g_queue_is_empty(&c->xmit_queue);
- g_queue_foreach(&c->xmit_queue, (GFunc)spice_msg_out_unref, NULL);
- g_queue_clear(&c->xmit_queue);
- if (c->xmit_queue_wakeup_id) {
- g_source_remove(c->xmit_queue_wakeup_id);
- c->xmit_queue_wakeup_id = 0;
- }
- STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
- spice_channel_flushed(channel, was_empty);
-
- g_array_set_size(c->remote_common_caps, 0);
- g_array_set_size(c->remote_caps, 0);
- g_array_set_size(c->common_caps, 0);
- /* Restore our default capabilities in case the channel gets re-used */
- spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
- spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
- spice_channel_reset_capabilities(channel);
-
- if (c->state == SPICE_CHANNEL_STATE_SWITCHING)
- spice_session_set_migration_state(spice_channel_get_session(channel),
- SPICE_SESSION_MIGRATION_NONE);
-}
-
-/* system or coroutine context */
-G_GNUC_INTERNAL
-void spice_channel_reset(SpiceChannel *channel, gboolean migrating)
-{
- CHANNEL_DEBUG(channel, "reset %s", migrating ? "migrating" : "");
- SPICE_CHANNEL_GET_CLASS(channel)->channel_reset(channel, migrating);
-}
-
-/**
- * spice_channel_disconnect:
- * @channel:
- * @reason: a channel event emitted on main context (or #SPICE_CHANNEL_NONE)
- *
- * Close the socket and reset connection specific data. Finally, emit
- * @reason #SpiceChannel::channel-event on main context if not
- * #SPICE_CHANNEL_NONE.
- **/
-void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
-{
- SpiceChannelPrivate *c;
-
- CHANNEL_DEBUG(channel, "channel disconnect %d", reason);
-
- g_return_if_fail(SPICE_IS_CHANNEL(channel));
- g_return_if_fail(channel->priv != NULL);
-
- c = channel->priv;
-
- if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
- return;
-
- if (reason == SPICE_CHANNEL_SWITCHING)
- c->state = SPICE_CHANNEL_STATE_SWITCHING;
-
- c->has_error = TRUE; /* break the loop */
-
- if (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
- c->state = SPICE_CHANNEL_STATE_READY;
- } else
- spice_channel_wakeup(channel, TRUE);
-
- if (reason != SPICE_CHANNEL_NONE)
- g_signal_emit(G_OBJECT(channel), signals[SPICE_CHANNEL_EVENT], 0, reason);
-}
-
-static gboolean test_capability(GArray *caps, guint32 cap)
-{
- guint32 c, word_index = cap / 32;
- gboolean ret;
-
- if (caps == NULL)
- return FALSE;
-
- if (caps->len < word_index + 1)
- return FALSE;
-
- c = g_array_index(caps, guint32, word_index);
- ret = (c & (1 << (cap % 32))) != 0;
-
- SPICE_DEBUG("test cap %d in 0x%X: %s", cap, c, ret ? "yes" : "no");
- return ret;
-}
-
-/**
- * spice_channel_test_capability:
- * @channel:
- * @cap:
- *
- * Test availability of remote "channel kind capability".
- *
- * Returns: %TRUE if @cap (channel kind capability) is available.
- **/
-gboolean spice_channel_test_capability(SpiceChannel *self, guint32 cap)
-{
- SpiceChannelPrivate *c;
-
- g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
-
- c = self->priv;
- return test_capability(c->remote_caps, cap);
-}
-
-/**
- * spice_channel_test_common_capability:
- * @channel:
- * @cap:
- *
- * Test availability of remote "common channel capability".
- *
- * Returns: %TRUE if @cap (common channel capability) is available.
- **/
-gboolean spice_channel_test_common_capability(SpiceChannel *self, guint32 cap)
-{
- SpiceChannelPrivate *c;
-
- g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
-
- c = self->priv;
- return test_capability(c->remote_common_caps, cap);
-}
-
-static void set_capability(GArray *caps, guint32 cap)
-{
- guint word_index = cap / 32;
-
- g_return_if_fail(caps != NULL);
-
- if (caps->len <= word_index)
- g_array_set_size(caps, word_index + 1);
-
- g_array_index(caps, guint32, word_index) =
- g_array_index(caps, guint32, word_index) | (1 << (cap % 32));
-}
-
-/**
- * spice_channel_set_capability:
- * @channel:
- * @cap: a capability
- *
- * Enable specific channel-kind capability.
- * Deprecated: 0.13: this function has been removed
- **/
-#undef spice_channel_set_capability
-void spice_channel_set_capability(SpiceChannel *channel, guint32 cap)
-{
- SpiceChannelPrivate *c;
-
- g_return_if_fail(SPICE_IS_CHANNEL(channel));
-
- c = channel->priv;
- set_capability(c->caps, cap);
-}
-
-G_GNUC_INTERNAL
-void spice_caps_set(GArray *caps, guint32 cap, const gchar *desc)
-{
- g_return_if_fail(caps != NULL);
- g_return_if_fail(desc != NULL);
-
- if (g_strcmp0(g_getenv(desc), "0") == 0)
- return;
-
- set_capability(caps, cap);
-}
-
-G_GNUC_INTERNAL
-SpiceSession* spice_channel_get_session(SpiceChannel *channel)
-{
- g_return_val_if_fail(SPICE_IS_CHANNEL(channel), NULL);
-
- return channel->priv->session;
-}
-
-G_GNUC_INTERNAL
-enum spice_channel_state spice_channel_get_state(SpiceChannel *channel)
-{
- g_return_val_if_fail(SPICE_IS_CHANNEL(channel),
- SPICE_CHANNEL_STATE_UNCONNECTED);
-
- return channel->priv->state;
-}
-
-G_GNUC_INTERNAL
-void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs)
-{
- SpiceChannelPrivate *c = channel->priv;
- SpiceChannelPrivate *s = swap->priv;
-
- g_return_if_fail(c != NULL);
- g_return_if_fail(s != NULL);
-
- g_return_if_fail(s->session != NULL);
- g_return_if_fail(s->sock != NULL);
-
-#define SWAP(Field) ({ \
- typeof (c->Field) Field = c->Field; \
- c->Field = s->Field; \
- s->Field = Field; \
-})
-
- /* TODO: split channel in 2 objects: a controller and a swappable
- state object */
- SWAP(sock);
- SWAP(conn);
- SWAP(in);
- SWAP(out);
- SWAP(ctx);
- SWAP(ssl);
- SWAP(sslverify);
- SWAP(tls);
- SWAP(use_mini_header);
- if (swap_msgs) {
- SWAP(xmit_queue);
- SWAP(xmit_queue_blocked);
- SWAP(in_serial);
- SWAP(out_serial);
- }
- SWAP(caps);
- SWAP(common_caps);
- SWAP(remote_caps);
- SWAP(remote_common_caps);
-#if HAVE_SASL
- SWAP(sasl_conn);
- SWAP(sasl_decoded);
- SWAP(sasl_decoded_length);
- SWAP(sasl_decoded_offset);
-#endif
-}
-
-/* coroutine context */
-static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
-{
- SpiceChannelClass *klass = SPICE_CHANNEL_GET_CLASS(channel);
- int type = spice_msg_in_type(msg);
- spice_msg_handler handler;
-
- g_return_if_fail(type < klass->handlers->len);
- if (type > SPICE_MSG_BASE_LAST && channel->priv->disable_channel_msg)
- return;
-
- handler = g_array_index(klass->handlers, spice_msg_handler, type);
- g_return_if_fail(handler != NULL);
- handler(channel, msg);
-}
-
-static void spice_channel_reset_capabilities(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
- g_array_set_size(c->caps, 0);
-
- if (SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities) {
- SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities(channel);
- }
-}
-
-static void spice_channel_send_migration_handshake(SpiceChannel *channel)
-{
- SpiceChannelPrivate *c = channel->priv;
-
- if (SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake) {
- SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake(channel);
- } else {
- c->state = SPICE_CHANNEL_STATE_MIGRATING;
- }
-}
-
-/**
- * spice_channel_flush_async:
- * @channel: a #SpiceChannel
- * @cancellable: (allow-none): optional GCancellable object, %NULL to ignore
- * @callback: (scope async): callback to call when the request is satisfied
- * @user_data: (closure): the data to pass to callback function
- *
- * Forces an asynchronous write of all user-space buffered data for
- * the given channel.
- *
- * When the operation is finished callback will be called. You can
- * then call spice_channel_flush_finish() to get the result of the
- * operation.
- *
- * Since: 0.15
- **/
-void spice_channel_flush_async(SpiceChannel *self, GCancellable *cancellable,
- GAsyncReadyCallback callback, gpointer user_data)
-{
- GSimpleAsyncResult *simple;
- SpiceChannelPrivate *c;
- gboolean was_empty;
-
- g_return_if_fail(SPICE_IS_CHANNEL(self));
- c = self->priv;
-
- if (c->state != SPICE_CHANNEL_STATE_READY) {
- g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
- SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
- "The channel is not ready yet");
- return;
- }
-
- simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
- spice_channel_flush_async);
-
- STATIC_MUTEX_LOCK(c->xmit_queue_lock);
- was_empty = g_queue_is_empty(&c->xmit_queue);
- STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
- if (was_empty) {
- g_simple_async_result_set_op_res_gboolean(simple, TRUE);
- g_simple_async_result_complete_in_idle(simple);
- g_object_unref(simple);
- return;
- }
-
- c->flushing = g_slist_append(c->flushing, simple);
-}
-
-/**
- * spice_channel_flush_finish:
- * @channel: a #SpiceChannel
- * @result: a #GAsyncResult
- * @error: a #GError location to store the error occurring, or %NULL
- * to ignore.
- *
- * Finishes flushing a channel.
- *
- * Returns: %TRUE if flush operation succeeded, %FALSE otherwise.
- * Since: 0.15
- **/
-gboolean spice_channel_flush_finish(SpiceChannel *self, GAsyncResult *result,
- GError **error)
-{
- GSimpleAsyncResult *simple;
-
- g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
- g_return_val_if_fail(result != NULL, FALSE);
-
- simple = (GSimpleAsyncResult *)result;
-
- if (g_simple_async_result_propagate_error(simple, error))
- return -1;
-
- g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self),
- spice_channel_flush_async), FALSE);
-
- CHANNEL_DEBUG(self, "flushed finished!");
- return g_simple_async_result_get_op_res_gboolean(simple);
-}