[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