summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac56
-rw-r--r--doc/reference/spice-gtk-sections.txt17
-rw-r--r--doc/reference/spice-gtk.types2
-rw-r--r--src/Makefile.am5
-rw-r--r--src/channel-ssh.c482
-rw-r--r--src/channel-ssh.h68
-rw-r--r--src/map-file1
-rw-r--r--src/spice-channel.c10
-rw-r--r--src/spice-client.h1
-rw-r--r--src/spice-glib-sym-file1
10 files changed, 629 insertions, 14 deletions
diff --git a/configure.ac b/configure.ac
index 4860fef..e5cea25 100644
--- a/configure.ac
+++ b/configure.ac
@@ -269,6 +269,33 @@ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 > 2.0.0)
AC_SUBST(GTHREAD_CFLAGS)
AC_SUBST(GTHREAD_LIBS)
+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_libssh="no"
+else
+ PKG_CHECK_MODULES(LIBSSH,
+ [libssh >= 0.8 glib-2.0 >= 2.43.90],
+ [have_libssh=yes],
+ [have_libssh=no])
+ AC_SUBST(LIBSSH_CFLAGS)
+ AC_SUBST(LIBSSH_LIBS)
+
+ if test "x$have_libssh" = "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_libssh" = "xyes"],
+ AC_DEFINE([USE_LIBSSH], [1], [Define if supporting libssh]))
+
+AM_CONDITIONAL([WITH_LIBSSH], [test "x$have_libssh" = "xyes"])
+
AC_ARG_ENABLE([webdav],
AS_HELP_STRING([--enable-webdav=@<:@auto/yes/no@:>@],
[Enable webdav support @<:@default=auto@:>@]),
@@ -739,20 +766,21 @@ AC_MSG_NOTICE([
Spice-Gtk $VERSION
==============
- prefix: ${prefix}
- c compiler: ${CC}
- Target: ${red_target}
-
- Gtk: ${with_gtk}
- Coroutine: ${with_coroutine}
- PulseAudio: ${enable_pulse}
- GStreamer Audio: ${have_gstaudio}
- SASL support: ${enable_sasl}
- Smartcard support: ${have_smartcard}
- USB redirection support: ${have_usbredir} ${with_usbredir_hotplug}
- DBus: ${have_dbus}
- WebDAV support: ${have_phodav}
- LZ4 support: ${enable_lz4}
+ prefix: ${prefix}
+ c compiler: ${CC}
+ Target: ${red_target}
+
+ Gtk: ${with_gtk}
+ Coroutine: ${with_coroutine}
+ PulseAudio: ${enable_pulse}
+ GStreamer Audio: ${have_gstaudio}
+ SASL support: ${enable_sasl}
+ Smartcard support: ${have_smartcard}
+ USB redirection support: ${have_usbredir} ${with_usbredir_hotplug}
+ DBus: ${have_dbus}
+ WebDAV support: ${have_phodav}
+ LZ4 support: ${enable_lz4}
+ ssh-agent forward support: ${have_libssh}
Now type 'make' to build $PACKAGE
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index f156a3f..214e5de 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -517,3 +517,20 @@ SPICE_FILE_TRANSFER_TASK_GET_CLASS
<SUBSECTION Private>
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 46162b3..43217be 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -103,6 +103,7 @@ SPICE_COMMON_CPPFLAGS = \
$(GUDEV_CFLAGS) \
$(SOUP_CFLAGS) \
$(PHODAV_CFLAGS) \
+ $(LIBSSH_CFLAGS) \
$(LZ4_CFLAGS) \
$(NULL)
@@ -216,6 +217,7 @@ libspice_client_glib_2_0_la_LIBADD = \
$(USBREDIR_LIBS) \
$(GUDEV_LIBS) \
$(PHODAV_LIBS) \
+ $(LIBSSH_LIBS) \
$(NULL)
if WITH_POLKIT
@@ -264,6 +266,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 \
@@ -314,6 +317,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 \
@@ -635,6 +639,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..8c23c5c
--- /dev/null
+++ b/src/channel-ssh.c
@@ -0,0 +1,482 @@
+/* -*- 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-common.h"
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-marshal.h"
+#include "glib-compat.h"
+#include "vmcstream.h"
+#include "giopipe.h"
+
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <libssh/libssh.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.31
+ */
+
+#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;
+};
+
+G_DEFINE_TYPE (SpiceSshChannel, spice_ssh_channel, SPICE_TYPE_PORT_CHANNEL)
+
+#ifdef USE_LIBSSH
+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 (socket_t 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);
+
+ 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;
+ guint8 payload[1024] = {0};
+ guint32 len;
+ ssh_buffer reply = NULL;
+ 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));
+
+ reply = ssh_buffer_new ();
+ len = c->msg_out.len;
+
+ while (len > 0) {
+ gsize n = len;
+ if (n > sizeof (payload)) {
+ n = sizeof (payload);
+ }
+ if (atomicio (c->auth_socket, payload, n, 1) != n) {
+ g_warning ("Error reading response from authentication socket.");
+ return;
+ }
+ if (ssh_buffer_add_data (reply, payload, n) < 0) {
+ g_warning ("Not enough space\n");
+ return;
+ }
+ len -= n;
+ }
+
+ /* payload must be freed inside payload_write_cb () */
+ c->msg_out.payload = g_malloc0 (c->msg_out.len * sizeof (guint8));
+
+ ssh_buffer_get_data (reply, c->msg_out.payload, c->msg_out.len);
+ ssh_buffer_free (reply);
+ reply = NULL;
+
+ 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, ssh_buffer request)
+{
+ SpiceSshChannelPrivate *c = self->priv;
+ guint32 payload = 0;
+ GOutputStream *ostream = NULL;
+ guint32 size = ssh_buffer_get_len (request);
+
+ agent_put_u32(&payload, size);
+
+ /* send length and then the request packet */
+ if (atomicio (c->auth_socket, &payload, 4, 0) == 4) {
+ if (atomicio (c->auth_socket, ssh_buffer_get (request), size, 0) != size) {
+ 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: %d\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);
+ ssh_buffer request = NULL;
+ 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);
+
+ request = ssh_buffer_new ();
+ ssh_buffer_add_data (request, c->msg_in.payload, c->msg_in.len);
+
+ g_free (c->msg_in.payload);
+ c->msg_in.payload = NULL;
+ c->msg_in.len = 0;
+
+ agent_talk (self, request);
+
+ ssh_buffer_free (request);
+ request = NULL;
+}
+
+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_LIBSSH
+ SpiceSshChannelPrivate *c = self->priv;
+ GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM (c->stream));
+
+ 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_cancellable_reset (c->cancellable);
+ start_virtio_read (self);
+ } else {
+ g_cancellable_cancel (c->cancellable);
+ }
+}
+
+static gint
+spice_ssh_channel_get_auth_socket (void)
+{
+ const gchar *authsocket;
+ gint sock;
+ struct sockaddr_un sunaddr;
+
+ /**
+ * FIXME:
+ * adapt to the new SSH_AUTH_SOCK's format
+ **/
+ 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));
+
+ 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;
+
+ 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 62cdb51..0a77e69 100644
--- a/src/map-file
+++ b/src/map-file
@@ -108,6 +108,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 a2a6cb9..50ed738 100644
--- a/src/spice-channel.c
+++ b/src/spice-channel.c
@@ -1935,6 +1935,7 @@ static const char *to_string[] = {
[ SPICE_CHANNEL_USBREDIR ] = "usbredir",
[ SPICE_CHANNEL_PORT ] = "port",
[ SPICE_CHANNEL_WEBDAV ] = "webdav",
+ [ SPICE_CHANNEL_SSH ] = "ssh",
};
/**
@@ -1998,6 +1999,9 @@ gchar *spice_channel_supported_string(void)
#ifdef USE_PHODAV
spice_channel_type_to_string(SPICE_CHANNEL_WEBDAV),
#endif
+#ifdef USE_LIBSSH
+ spice_channel_type_to_string(SPICE_CHANNEL_SSH),
+#endif
NULL);
}
@@ -2068,6 +2072,12 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
break;
}
#endif
+#ifdef USE_LIBSSH
+ 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 ae365cd..e2dbdf9 100644
--- a/src/spice-glib-sym-file
+++ b/src/spice-glib-sym-file
@@ -85,6 +85,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