[Spice-devel] [PATCH 6/6] [spice-gtk] Add channel-ssh
Fabiano FidĂȘncio
fidencio at redhat.com
Sat Sep 3 11:49:12 UTC 2016
The brand new channel-ssh has been created in order to do the
communication between the guest and the client's ssh agent. With the new
agent and a little bit of configuration on the guest we are able to
forward the ssh-agent through a spice channel.
The configuration needed on the guest side (part of OpenSSH 7.3 release)
is quite simple, just add in the guest's ssh config file:
Host: hostname
IdentityAgent: /var/run/spice-ssh-agentd/spice-ssh-agent-sock
It will forward the agent when connecting to that specify hostname.
Signed-off-by: Fabiano FidĂȘncio <fidencio at redhat.com>
---
configure.ac | 26 ++
doc/reference/spice-gtk-sections.txt | 17 ++
doc/reference/spice-gtk.types | 2 +
src/Makefile.am | 3 +
src/channel-ssh.c | 454 +++++++++++++++++++++++++++++++++++
src/channel-ssh.h | 68 ++++++
src/map-file | 1 +
src/spice-channel.c | 10 +
src/spice-client.h | 1 +
src/spice-glib-sym-file | 1 +
10 files changed, 583 insertions(+)
create mode 100644 src/channel-ssh.c
create mode 100644 src/channel-ssh.h
diff --git a/configure.ac b/configure.ac
index f3e7f8d..5beb5c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -188,6 +188,31 @@ PKG_CHECK_MODULES(CAIRO, cairo >= 1.2.0)
PKG_CHECK_MODULES(GTHREAD, gthread-2.0)
+AC_ARG_ENABLE([ssh-agent-forward],
+ AS_HELP_STRING([--enable-ssh-agent-forward=@<:@auto/yes/no@:>@],
+ [Enable ssh-agent-forward support @<:@default=auto@:>@]),
+ [],
+ [enable_ssh_agent_forward="auto"])
+
+
+if test "x$enable_ssh_agent_forward" = "xno"; then
+ have_ssh_agent_forward="no"
+else
+ PKG_CHECK_MODULES(GIO,
+ [glib-2.0 >= 2.43.90 gio-2.0 > 2.43.90],
+ [have_ssh_agent_forward=yes],
+ [have_ssh_agent_forward=no])
+
+ if test "x$have_ssh_agent_forward" = "xno" && test "x$enable_ssh_agent_forward" = "xyes"; then
+ AC_MSG_ERROR([ssh-agent-forward support explicitly requested, but some required packages are not available])
+ fi
+fi
+
+AS_IF([test "x$have_ssh_agent_forward" = "xyes"],
+ AC_DEFINE([USE_SSH_AGENT_FORWARD], [1], [Define if supporting ssh-agent-forward]))
+
+AM_CONDITIONAL([WITH_SSH_AGENT_FORWARD], [test "x$have_ssh_agent_forward" = "xyes"])
+
AC_ARG_ENABLE([webdav],
AS_HELP_STRING([--enable-webdav=@<:@auto/yes/no@:>@],
[Enable webdav support @<:@default=auto@:>@]),
@@ -618,6 +643,7 @@ AC_MSG_NOTICE([
DBus: ${have_dbus}
WebDAV support: ${have_phodav}
LZ4 support: ${have_lz4}
+ ssh-agent forward support: ${have_ssh_agent_forward}
Now type 'make' to build $PACKAGE
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index 386e737..98be852 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -527,3 +527,20 @@ SpiceFileTransferTask
SpiceFileTransferTaskClass
SpiceFileTransferTaskPrivate
</SECTION>
+
+<SECTION>
+<FILE>channel-ssh</FILE>
+<TITLE>SpiceSshChannel</TITLE>
+SpiceSshChannel
+SpiceSshChannelClass
+<SUBSECTION Standard>
+SPICE_IS_SSH_CHANNEL
+SPICE_IS_SSH_CHANNEL_CLASS
+SPICE_TYPE_SSH_CHANNEL
+SPICE_SSH_CHANNEL
+SPICE_SSH_CHANNEL_CLASS
+SPICE_SSH_CHANNEL_GET_CLASS
+spice_ssh_channel_get_type
+<SUBSECTION Private>
+SpiceSshChannelPrivate
+</SECTION>
diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
index e14ae1b..ea07d5b 100644
--- a/doc/reference/spice-gtk.types
+++ b/doc/reference/spice-gtk.types
@@ -12,6 +12,7 @@
#include "channel-playback.h"
#include "channel-record.h"
#include "channel-smartcard.h"
+#include "channel-ssh.h"
#include "channel-usbredir.h"
#include "channel-webdav.h"
#include "spice-gtk-session.h"
@@ -40,6 +41,7 @@ spice_session_verify_get_type
spice_smartcard_channel_get_type
spice_smartcard_manager_get_type
spice_session_verify_get_type
+spice_ssh_channel_get_type
spice_usbredir_channel_get_type
spice_usb_device_get_type
spice_usb_device_manager_get_type
diff --git a/src/Makefile.am b/src/Makefile.am
index 7542580..87fdfe9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -254,6 +254,7 @@ libspice_client_glib_2_0_la_SOURCES = \
channel-port.c \
channel-record.c \
channel-smartcard.c \
+ channel-ssh.c \
channel-usbredir.c \
channel-usbredir-priv.h \
smartcard-manager.c \
@@ -306,6 +307,7 @@ libspice_client_glibinclude_HEADERS = \
channel-port.h \
channel-record.h \
channel-smartcard.h \
+ channel-ssh.h \
channel-usbredir.h \
channel-webdav.h \
usb-device-manager.h \
@@ -599,6 +601,7 @@ glib_introspection_files = \
channel-port.c \
channel-record.c \
channel-smartcard.c \
+ channel-ssh.c \
channel-usbredir.c \
smartcard-manager.c \
usb-device-manager.c \
diff --git a/src/channel-ssh.c b/src/channel-ssh.c
new file mode 100644
index 0000000..0d3c304
--- /dev/null
+++ b/src/channel-ssh.c
@@ -0,0 +1,454 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2015 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-channel-priv.h"
+#include "spice-session-priv.h"
+#include "vmcstream.h"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+/**
+ * SECTION:channel-ssh
+ * @short_description: forwards the client ssh-agent
+ * @title: Ssh Channel
+ * @section_id:
+ * @see_also: #SpiceChannel
+ * @stability: Stable
+ * @include: channel-ssh.h
+ *
+ * The "ssh" channel forwards the client ssh-agent to a guest.
+ * The underlying protocol implemented is:
+ * https://tools.ietf.org/html/draft-ietf-secsh-agent-02
+ *
+ * Since: 0.33
+ */
+
+#define SPICE_SSH_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_SSH_CHANNEL, SpiceSshChannelPrivate))
+
+typedef struct _msg {
+ guint32 len;
+ guint8 *payload;
+} msg;
+
+struct _SpiceSshChannelPrivate {
+ SpiceVmcStream *stream;
+ int auth_socket;
+ GCancellable *cancellable;
+ msg msg_in;
+ msg msg_out;
+ gboolean demuxing;
+};
+
+G_DEFINE_TYPE (SpiceSshChannel, spice_ssh_channel, SPICE_TYPE_PORT_CHANNEL)
+
+#ifdef USE_SSH_AGENT_FORWARD
+static void start_virtio_read (SpiceSshChannel *self);
+
+static void
+agent_put_u32( void *vp, guint32 v) {
+ guint8 *p = vp;
+
+ p[0] = (guint8) (v >> 24) & 0xff;
+ p[1] = (guint8) (v >> 16) & 0xff;
+ p[2] = (guint8) (v >> 8) & 0xff;
+ p[3] = (guint8) v & 0xff;
+}
+
+static
+guint32 agent_get_u32 (const void *vp) {
+ const guint8 *p = vp;
+ guint32 v;
+
+ v = (guint32) p[0] << 24;
+ v |= (guint32) p[1] << 16;
+ v |= (guint32) p[2] << 8;
+ v |= (guint32) p[3];
+
+ return v;
+}
+
+#ifndef POLLIN
+#define POLLIN 0x001 /* There is data to read. */
+#endif
+#ifndef POLLOUT
+#define POLLOUT 0x004 /* Writing now will not block. */
+#endif
+
+static gsize
+atomicio (int fd, void *buf, gsize n, int do_read) {
+ gchar *b = buf;
+ gsize pos = 0;
+ gssize res;
+ struct pollfd pfd;
+
+ pfd.fd = fd;
+ pfd.events = do_read ? POLLIN : POLLOUT;
+
+ while (n > pos) {
+ if (do_read) {
+ res = read (fd, b + pos, n - pos);
+ } else {
+ res = write (fd, b + pos, n - pos);
+ }
+ switch (res) {
+ case -1:
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN) {
+ poll (&pfd, 1, -1);
+ continue;
+ }
+ return 0;
+ case 0:
+ /* read returns 0 on end-of-file */
+ errno = do_read ? 0 : EPIPE;
+ return pos;
+ default:
+ pos += (gsize) res;
+ }
+ }
+
+ return pos;
+}
+
+static void
+payload_write_cb (GObject *source_object, GAsyncResult *res, gpointer data)
+{
+ GOutputStream *ostream = G_OUTPUT_STREAM (source_object);
+ SpiceSshChannel *self = data;
+ SpiceSshChannelPrivate *c = self->priv;
+ gsize size;
+ GError *error = NULL;
+
+ g_output_stream_write_all_finish (ostream, res, &size, &error);
+
+ g_free (c->msg_out.payload);
+ c->msg_out.payload = NULL;
+
+ if (error != NULL) {
+ g_warning ("error: %s", error->message);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ g_return_if_fail (c->msg_out.len == size);
+
+ c->demuxing = FALSE;
+
+ start_virtio_read (self);
+}
+
+static void
+size_write_cb (GObject *source_object, GAsyncResult *res, gpointer data)
+{
+ GOutputStream *ostream = G_OUTPUT_STREAM (source_object);
+ SpiceSshChannel *self = data;
+ SpiceSshChannelPrivate *c = self->priv;
+ gsize size;
+ GError *error = NULL;
+
+ g_output_stream_write_all_finish (ostream, res, &size, &error);
+ if (error != NULL) {
+ g_warning ("error: %s", error->message);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ g_return_if_fail (size == sizeof (guint32));
+
+ /* payload must be freed inside payload_write_cb () */
+ c->msg_out.payload = g_malloc0 (c->msg_out.len * sizeof (guint8));
+
+ if (atomicio (c->auth_socket, c->msg_out.payload, c->msg_out.len, 1) != c->msg_out.len) {
+ g_warning ("Error reading response from authentication socket.");
+ return;
+ }
+
+ g_output_stream_write_all_async (
+ ostream,
+ c->msg_out.payload,
+ c->msg_out.len,
+ G_PRIORITY_DEFAULT,
+ c->cancellable,
+ payload_write_cb,
+ self);
+}
+
+static void
+agent_talk (SpiceSshChannel *self)
+{
+ SpiceSshChannelPrivate *c = self->priv;
+ guint32 payload;
+ GOutputStream *ostream = NULL;
+
+ agent_put_u32(&payload, c->msg_in.len);
+
+ /* send length and then the request packet */
+ if (atomicio (c->auth_socket, &payload, 4, 0) == 4) {
+ if (atomicio (c->auth_socket, c->msg_in.payload, c->msg_in.len, 0) != c->msg_in.len) {
+ g_warning ("atomicio sending request failed: %s", g_strerror (errno));
+ return;
+ }
+ } else {
+ g_warning ("atomicio sending request length failed: %s", g_strerror (errno));
+ return;
+ }
+
+ /* wait for response, read the length of the response packet */
+ if (atomicio (c->auth_socket, &payload, 4, 1) != 4) {
+ g_warning ("atomicio read response length failed: %s", g_strerror (errno));
+ return;
+ }
+
+ c->msg_out.len = agent_get_u32(&payload);
+ if (c->msg_out.len > 256 * 1024) {
+ g_warning ("Authentication response is too long: %"PRIu32"\n", c->msg_out.len);
+ return;
+ }
+
+ ostream = g_io_stream_get_output_stream (G_IO_STREAM (c->stream));
+ g_output_stream_write_all_async (
+ ostream,
+ &c->msg_out.len,
+ sizeof (guint32),
+ G_PRIORITY_DEFAULT,
+ c->cancellable,
+ size_write_cb,
+ self);
+}
+
+static void
+payload_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ SpiceSshChannel *self = user_data;
+ SpiceSshChannelPrivate *c = self->priv;
+ GInputStream *istream = G_INPUT_STREAM (source_object);
+ gssize size;
+ GError *error = NULL;
+
+ size = spice_vmc_input_stream_read_all_finish (istream, res, &error);
+ if (error != NULL) {
+ g_warning ("error: %s\n", error->message);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ g_return_if_fail (size == c->msg_in.len);
+
+ agent_talk (self);
+
+ g_clear_pointer(&c->msg_in.payload, g_free);
+ c->msg_in.len = 0;
+}
+
+static void
+size_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ SpiceSshChannel *self = user_data;
+ SpiceSshChannelPrivate *c = self->priv;
+ GInputStream *istream = G_INPUT_STREAM (source_object);
+ gssize size;
+ GError *error = NULL;
+
+ size = spice_vmc_input_stream_read_all_finish (istream, res, &error);
+ if (error != NULL) {
+ g_warning ("error: %s\n", error->message);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ g_return_if_fail (size == sizeof (guint32));
+
+ /* payload must be freed inside payload_read_cb () */
+ c->msg_in.payload = g_malloc0 (c->msg_in.len * sizeof (guint8));
+
+ spice_vmc_input_stream_read_all_async (
+ istream,
+ c->msg_in.payload,
+ c->msg_in.len,
+ G_PRIORITY_DEFAULT,
+ c->cancellable,
+ payload_read_cb,
+ self);
+}
+#endif
+
+static void
+start_virtio_read (SpiceSshChannel *self)
+{
+#ifdef USE_SSH_AGENT_FORWARD
+ SpiceSshChannelPrivate *c = self->priv;
+ GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (c->stream));
+
+ if (c->demuxing)
+ return;
+
+ c->demuxing = TRUE;
+
+ CHANNEL_DEBUG (self, "start virtio read");
+ spice_vmc_input_stream_read_all_async (
+ istream,
+ &c->msg_in.len,
+ sizeof (guint32),
+ G_PRIORITY_DEFAULT,
+ c->cancellable,
+ size_read_cb,
+ self);
+#endif
+}
+
+static void
+port_event (SpiceSshChannel *self, gint event)
+{
+ SpiceSshChannelPrivate *c = self->priv;
+
+ CHANNEL_DEBUG (self, "port event: %d", event);
+ if (event == SPICE_PORT_EVENT_OPENED) {
+ g_clear_object (&c->cancellable);
+ c->cancellable = g_cancellable_new();
+ start_virtio_read (self);
+ } else {
+ g_cancellable_cancel (c->cancellable);
+
+ c->demuxing = FALSE;
+ }
+}
+
+static gint
+spice_ssh_channel_get_auth_socket (void)
+{
+ const gchar *authsocket;
+ gint sock;
+ struct sockaddr_un sunaddr;
+
+ authsocket = g_getenv ("SSH_AUTH_SOCK");
+ if (authsocket == NULL)
+ return -1;
+
+ memset (&sunaddr, 0, sizeof (sunaddr));
+ sunaddr.sun_family = AF_UNIX;
+ strncpy (sunaddr.sun_path, authsocket, sizeof (sunaddr.sun_path));
+
+ if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -1;
+
+ if (fcntl (sock, F_SETFD, FD_CLOEXEC) == -1 ||
+ connect (sock, (struct sockaddr *)&sunaddr, sizeof (sunaddr)) < 0) {
+ close (sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+static void
+spice_ssh_channel_init (SpiceSshChannel *channel)
+{
+ SpiceSshChannelPrivate *c = SPICE_SSH_CHANNEL_GET_PRIVATE (channel);
+
+ channel->priv = c;
+ c->stream = spice_vmc_stream_new (SPICE_CHANNEL (channel));
+
+ c->auth_socket = spice_ssh_channel_get_auth_socket ();
+ c->cancellable = g_cancellable_new ();
+}
+
+static void
+spice_ssh_channel_dispose (GObject *object)
+{
+ SpiceSshChannelPrivate *c = SPICE_SSH_CHANNEL (object)->priv;
+
+ if (!g_cancellable_is_cancelled(c->cancellable))
+ g_cancellable_cancel (c->cancellable);
+ g_clear_object (&c->cancellable);
+ g_clear_object (&c->stream);
+ close (c->auth_socket);
+
+ G_OBJECT_CLASS (spice_ssh_channel_parent_class)->dispose (object);
+}
+
+/* coroutine context */
+static void
+ssh_handle_msg (SpiceChannel *channel, SpiceMsgIn *in)
+{
+ SpiceSshChannel *self = SPICE_SSH_CHANNEL (channel);
+ SpiceSshChannelPrivate *c = self->priv;
+ gint size;
+ guint8 *buf;
+
+ buf = spice_msg_in_raw (in, &size);
+ CHANNEL_DEBUG (channel, "len:%d buf:%p", size, buf);
+
+ spice_vmc_input_stream_co_data (
+ SPICE_VMC_INPUT_STREAM (
+ g_io_stream_get_input_stream (G_IO_STREAM (c->stream))),
+ buf,
+ size);
+}
+
+/* coroutine context */
+static void
+spice_ssh_handle_msg (SpiceChannel *channel, SpiceMsgIn *msg)
+{
+ gint type = spice_msg_in_type (msg);
+ SpiceChannelClass *parent_class;
+
+ parent_class = SPICE_CHANNEL_CLASS (spice_ssh_channel_parent_class);
+
+ if (type == SPICE_MSG_SPICEVMC_DATA)
+ ssh_handle_msg (channel, msg);
+ else if (parent_class->handle_msg)
+ parent_class->handle_msg (channel, msg);
+ else
+ g_return_if_reached ();
+}
+
+static void
+spice_ssh_channel_up (SpiceChannel *channel)
+{
+ CHANNEL_DEBUG (channel, "up");
+}
+
+static void
+spice_ssh_channel_class_init (SpiceSshChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS (klass);
+
+ gobject_class->dispose = spice_ssh_channel_dispose;
+ channel_class->handle_msg = spice_ssh_handle_msg;
+ channel_class->channel_up = spice_ssh_channel_up;
+
+ g_signal_override_class_handler ("port-event",
+ SPICE_TYPE_SSH_CHANNEL,
+ G_CALLBACK (port_event));
+
+ g_type_class_add_private (klass, sizeof (SpiceSshChannelPrivate));
+}
diff --git a/src/channel-ssh.h b/src/channel-ssh.h
new file mode 100644
index 0000000..a5e358a
--- /dev/null
+++ b/src/channel-ssh.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2015 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/>.
+*/
+#ifndef __SPICE_SSH_CHANNEL_H__
+#define __SPICE_SSH_CHANNEL_H__
+
+#include <gio/gio.h>
+#include "spice-client.h"
+#include "channel-port.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_SSH_CHANNEL (spice_ssh_channel_get_type())
+#define SPICE_SSH_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_SSH_CHANNEL, SpiceSshChannel))
+#define SPICE_SSH_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_SSH_CHANNEL, SpiceSshChannelClass))
+#define SPICE_IS_SSH_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_SSH_CHANNEL))
+#define SPICE_IS_SSH_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_SSH_CHANNEL))
+#define SPICE_SSH_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_SSH_CHANNEL, SpiceSshChannelClass))
+
+typedef struct _SpiceSshChannel SpiceSshChannel;
+typedef struct _SpiceSshChannelClass SpiceSshChannelClass;
+typedef struct _SpiceSshChannelPrivate SpiceSshChannelPrivate;
+
+/**
+ * SpiceSshChannel:
+ *
+ * The #SpiceSshChannel struct is opaque and should not be accessed directly.
+ */
+struct _SpiceSshChannel {
+ SpicePortChannel parent;
+
+ /*< private >*/
+ SpiceSshChannelPrivate *priv;
+ /* Do not add fields to this struct */
+};
+
+/**
+ * SpiceSshChannelClass:
+ * @parent_class: Parent class.
+ *
+ * Class structure for #SpiceSshChannel.
+ */
+struct _SpiceSshChannelClass {
+ SpicePortChannelClass parent_class;
+
+ /*< private >*/
+ /* Do not add fields to this struct */
+};
+
+GType spice_ssh_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_SSH_CHANNEL_H__ */
diff --git a/src/map-file b/src/map-file
index 3d92153..9091362 100644
--- a/src/map-file
+++ b/src/map-file
@@ -113,6 +113,7 @@ spice_smartcard_reader_get_type;
spice_smartcard_reader_insert_card;
spice_smartcard_reader_is_software;
spice_smartcard_reader_remove_card;
+spice_ssh_channel_get_type;
spice_uri_get_hostname;
spice_uri_get_password;
spice_uri_get_port;
diff --git a/src/spice-channel.c b/src/spice-channel.c
index 95662f3..41faa5a 100644
--- a/src/spice-channel.c
+++ b/src/spice-channel.c
@@ -2035,6 +2035,7 @@ static const char *to_string[] = {
[ SPICE_CHANNEL_USBREDIR ] = "usbredir",
[ SPICE_CHANNEL_PORT ] = "port",
[ SPICE_CHANNEL_WEBDAV ] = "webdav",
+ [ SPICE_CHANNEL_SSH ] = "ssh",
};
/**
@@ -2098,6 +2099,9 @@ gchar *spice_channel_supported_string(void)
#ifdef USE_PHODAV
spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
#endif
+#ifdef USE_SSH_AGENT_FORWARD
+ spice_channel_type_to_string(SPICE_CHANNEL_SSH),
+#endif
NULL);
}
@@ -2168,6 +2172,12 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
break;
}
#endif
+#ifdef USE_SSH_AGENT_FORWARD
+ case SPICE_CHANNEL_SSH: {
+ gtype = SPICE_TYPE_SSH_CHANNEL;
+ break;
+ }
+#endif
case SPICE_CHANNEL_PORT:
gtype = SPICE_TYPE_PORT_CHANNEL;
break;
diff --git a/src/spice-client.h b/src/spice-client.h
index 32b79ea..904fa18 100644
--- a/src/spice-client.h
+++ b/src/spice-client.h
@@ -46,6 +46,7 @@
#include "channel-usbredir.h"
#include "channel-port.h"
#include "channel-webdav.h"
+#include "channel-ssh.h"
#include "smartcard-manager.h"
#include "usb-device-manager.h"
diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file
index 473c5ca..a9c4f73 100644
--- a/src/spice-glib-sym-file
+++ b/src/spice-glib-sym-file
@@ -92,6 +92,7 @@ spice_smartcard_reader_get_type
spice_smartcard_reader_insert_card
spice_smartcard_reader_is_software
spice_smartcard_reader_remove_card
+spice_ssh_channel_get_type
spice_uri_get_hostname
spice_uri_get_password
spice_uri_get_port
--
2.7.4
More information about the Spice-devel
mailing list