diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk
index 80b94425a0..e51877648a 100644
--- a/build-aux/syntax-check.mk
+++ b/build-aux/syntax-check.mk
@@ -1872,7 +1872,7 @@ sc_group-qemu-caps:
# List all syntax-check exemptions:
exclude_file_name_regexp--sc_avoid_strcase = ^tools/vsh\.h$$
-_src1=libvirt-stream|qemu/qemu_monitor|util/vir(command|file|fdstream)|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon
+_src1=libvirt-stream|qemu/qemu_monitor|util/vir(command|file|fdstream)|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon|remote/remote_ssh_helper
_test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock|commandhelper
exclude_file_name_regexp--sc_avoid_write = \
^(src/($(_src1))|tools/virsh-console|tests/($(_test1)))\.c$$
diff --git a/libvirt.spec.in b/libvirt.spec.in
index 379cf0d9be..62b401bd08 100644
--- a/libvirt.spec.in
+++ b/libvirt.spec.in
@@ -1557,6 +1557,8 @@ exit 0
%attr(0755, root, root) %{_libexecdir}/libvirt_iohelper
+%attr(0755, root, root) %{_bindir}/virt-ssh-helper
+
%attr(0755, root, root) %{_sbindir}/libvirtd
%attr(0755, root, root) %{_sbindir}/virtproxyd
%attr(0755, root, root) %{_sbindir}/virtlogd
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 982db7b0e8..471af30b89 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -184,6 +184,7 @@
@SRCDIR@src/remote/remote_daemon_stream.c
@SRCDIR@src/remote/remote_driver.c
@SRCDIR@src/remote/remote_sockets.c
+@SRCDIR@src/remote/remote_ssh_helper.c
@SRCDIR@src/rpc/virkeepalive.c
@SRCDIR@src/rpc/virnetclient.c
@SRCDIR@src/rpc/virnetclientprogram.c
diff --git a/src/remote/meson.build b/src/remote/meson.build
index 91dd587cba..9ad2f6ab1c 100644
--- a/src/remote/meson.build
+++ b/src/remote/meson.build
@@ -51,6 +51,15 @@ remote_daemon_sources = files(
remote_daemon_generated = []
+virt_ssh_helper_sources = files(
+ 'remote_sockets.c',
+ 'remote_ssh_helper.c',
+)
+
+virt_ssh_helper_dep = [
+ src_dep,
+]
+
foreach name : [ 'remote', 'qemu', 'lxc' ]
protocol_x = '@0@_protocol.x'.format(name)
dispatch_h = '@0@_daemon_dispatch_stubs.h'.format(name)
@@ -278,6 +287,14 @@ if conf.has('WITH_REMOTE')
rename: [ '50-libvirt.rules' ],
)
endif
+
+ virt_helpers += {
+ 'name': 'virt-ssh-helper',
+ 'sources': [
+ virt_ssh_helper_sources
+ ],
+ 'install_dir': bindir,
+ }
endif
endif
diff --git a/src/remote/remote_ssh_helper.c b/src/remote/remote_ssh_helper.c
new file mode 100644
index 0000000000..0da55c1d1f
--- /dev/null
+++ b/src/remote/remote_ssh_helper.c
@@ -0,0 +1,425 @@
+/*
+ * remote_ssh_helper.c: a netcat replacement for proxying ssh tunnel to daemon
+ *
+ * Copyright (C) 2020 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
+ * .
+ */
+
+#include
+
+#include
+
+#include "rpc/virnetsocket.h"
+#include "viralloc.h"
+#include "virlog.h"
+#include "virgettext.h"
+#include "virfile.h"
+
+#include "remote_sockets.h"
+
+#define VIR_FROM_THIS VIR_FROM_REMOTE
+
+VIR_LOG_INIT("remote.remote_ssh_helper");
+
+struct virRemoteSSHHelperBuffer {
+ size_t length;
+ size_t offset;
+ char *data;
+};
+
+typedef struct virRemoteSSHHelper virRemoteSSHHelper;
+typedef virRemoteSSHHelper *virRemoteSSHHelperPtr;
+struct virRemoteSSHHelper {
+ bool quit;
+ virNetSocketPtr sock;
+ int stdinWatch;
+ int stdoutWatch;
+
+ struct virRemoteSSHHelperBuffer sockToTerminal;
+ struct virRemoteSSHHelperBuffer terminalToSock;
+};
+
+
+static void
+virRemoteSSHHelperShutdown(virRemoteSSHHelperPtr proxy)
+{
+ if (proxy->sock) {
+ virNetSocketRemoveIOCallback(proxy->sock);
+ virNetSocketClose(proxy->sock);
+ virObjectUnref(proxy->sock);
+ proxy->sock = NULL;
+ }
+ VIR_FREE(proxy->sockToTerminal.data);
+ VIR_FREE(proxy->terminalToSock.data);
+ if (proxy->stdinWatch != -1)
+ virEventRemoveHandle(proxy->stdinWatch);
+ if (proxy->stdoutWatch != -1)
+ virEventRemoveHandle(proxy->stdoutWatch);
+ proxy->stdinWatch = -1;
+ proxy->stdoutWatch = -1;
+ if (!proxy->quit)
+ proxy->quit = true;
+}
+
+
+static void
+virRemoteSSHHelperEventOnSocket(virNetSocketPtr sock,
+ int events,
+ void *opaque)
+{
+ virRemoteSSHHelperPtr proxy = opaque;
+
+ /* we got late event after proxy was shutdown */
+ if (!proxy->sock)
+ return;
+
+ if (events & VIR_EVENT_HANDLE_READABLE) {
+ size_t avail = proxy->sockToTerminal.length -
+ proxy->sockToTerminal.offset;
+ int got;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(proxy->sockToTerminal.data,
+ proxy->sockToTerminal.length + 1024) < 0) {
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+ proxy->sockToTerminal.length += 1024;
+ avail += 1024;
+ }
+
+ got = virNetSocketRead(sock,
+ proxy->sockToTerminal.data +
+ proxy->sockToTerminal.offset,
+ avail);
+ if (got == -2)
+ return; /* blocking */
+ if (got == 0) {
+ VIR_DEBUG("EOF on socket, shutting down");
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+ if (got < 0) {
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+ proxy->sockToTerminal.offset += got;
+ if (proxy->sockToTerminal.offset)
+ virEventUpdateHandle(proxy->stdoutWatch,
+ VIR_EVENT_HANDLE_WRITABLE);
+ }
+
+ if (events & VIR_EVENT_HANDLE_WRITABLE &&
+ proxy->terminalToSock.offset) {
+ ssize_t done;
+ size_t avail;
+ done = virNetSocketWrite(proxy->sock,
+ proxy->terminalToSock.data,
+ proxy->terminalToSock.offset);
+ if (done == -2)
+ return; /* blocking */
+ if (done < 0) {
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+ memmove(proxy->terminalToSock.data,
+ proxy->terminalToSock.data + done,
+ proxy->terminalToSock.offset - done);
+ proxy->terminalToSock.offset -= done;
+
+ avail = proxy->terminalToSock.length - proxy->terminalToSock.offset;
+ if (avail > 1024) {
+ ignore_value(VIR_REALLOC_N(proxy->terminalToSock.data,
+ proxy->terminalToSock.offset + 1024));
+ proxy->terminalToSock.length = proxy->terminalToSock.offset + 1024;
+ }
+ }
+ if (!proxy->terminalToSock.offset)
+ virNetSocketUpdateIOCallback(proxy->sock,
+ VIR_EVENT_HANDLE_READABLE);
+
+ if (events & VIR_EVENT_HANDLE_ERROR ||
+ events & VIR_EVENT_HANDLE_HANGUP) {
+ virRemoteSSHHelperShutdown(proxy);
+ }
+}
+
+
+static void
+virRemoteSSHHelperEventOnStdin(int watch G_GNUC_UNUSED,
+ int fd G_GNUC_UNUSED,
+ int events,
+ void *opaque)
+{
+ virRemoteSSHHelperPtr proxy = opaque;
+
+ /* we got late event after console was shutdown */
+ if (!proxy->sock)
+ return;
+
+ if (events & VIR_EVENT_HANDLE_READABLE) {
+ size_t avail = proxy->terminalToSock.length -
+ proxy->terminalToSock.offset;
+ int got;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(proxy->terminalToSock.data,
+ proxy->terminalToSock.length + 1024) < 0) {
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+ proxy->terminalToSock.length += 1024;
+ avail += 1024;
+ }
+
+ got = read(fd,
+ proxy->terminalToSock.data +
+ proxy->terminalToSock.offset,
+ avail);
+ if (got < 0) {
+ if (errno != EAGAIN) {
+ virReportSystemError(errno, "%s", _("cannot read from stdin"));
+ virRemoteSSHHelperShutdown(proxy);
+ }
+ return;
+ }
+ if (got == 0) {
+ VIR_DEBUG("EOF on stdin, shutting down");
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+
+ proxy->terminalToSock.offset += got;
+ if (proxy->terminalToSock.offset)
+ virNetSocketUpdateIOCallback(proxy->sock,
+ VIR_EVENT_HANDLE_READABLE |
+ VIR_EVENT_HANDLE_WRITABLE);
+ }
+
+ if (events & VIR_EVENT_HANDLE_ERROR) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error on stdin"));
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+
+ if (events & VIR_EVENT_HANDLE_HANGUP) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+}
+
+
+static void
+virRemoteSSHHelperEventOnStdout(int watch G_GNUC_UNUSED,
+ int fd,
+ int events,
+ void *opaque)
+{
+ virRemoteSSHHelperPtr proxy = opaque;
+
+ /* we got late event after console was shutdown */
+ if (!proxy->sock)
+ return;
+
+ if (events & VIR_EVENT_HANDLE_WRITABLE &&
+ proxy->sockToTerminal.offset) {
+ ssize_t done;
+ size_t avail;
+ done = write(fd,
+ proxy->sockToTerminal.data,
+ proxy->sockToTerminal.offset);
+ if (done < 0) {
+ if (errno != EAGAIN) {
+ virReportSystemError(errno, "%s", _("cannot write to stdout"));
+ virRemoteSSHHelperShutdown(proxy);
+ }
+ return;
+ }
+ memmove(proxy->sockToTerminal.data,
+ proxy->sockToTerminal.data + done,
+ proxy->sockToTerminal.offset - done);
+ proxy->sockToTerminal.offset -= done;
+
+ avail = proxy->sockToTerminal.length - proxy->sockToTerminal.offset;
+ if (avail > 1024) {
+ ignore_value(VIR_REALLOC_N(proxy->sockToTerminal.data,
+ proxy->sockToTerminal.offset + 1024));
+ proxy->sockToTerminal.length = proxy->sockToTerminal.offset + 1024;
+ }
+ }
+
+ if (!proxy->sockToTerminal.offset)
+ virEventUpdateHandle(proxy->stdoutWatch, 0);
+
+ if (events & VIR_EVENT_HANDLE_ERROR) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error stdout"));
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+
+ if (events & VIR_EVENT_HANDLE_HANGUP) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdout"));
+ virRemoteSSHHelperShutdown(proxy);
+ return;
+ }
+}
+
+
+static int
+virRemoteSSHHelperRun(virNetSocketPtr sock)
+{
+ int ret = -1;
+ virRemoteSSHHelper proxy = {
+ .sock = sock,
+ .stdinWatch = -1,
+ .stdoutWatch = -1,
+ };
+
+ virEventRegisterDefaultImpl();
+
+ if ((proxy.stdinWatch = virEventAddHandle(STDIN_FILENO,
+ VIR_EVENT_HANDLE_READABLE,
+ virRemoteSSHHelperEventOnStdin,
+ &proxy,
+ NULL)) < 0)
+ goto cleanup;
+
+ if ((proxy.stdoutWatch = virEventAddHandle(STDOUT_FILENO,
+ 0,
+ virRemoteSSHHelperEventOnStdout,
+ &proxy,
+ NULL)) < 0)
+ goto cleanup;
+
+ if (virNetSocketAddIOCallback(proxy.sock,
+ VIR_EVENT_HANDLE_READABLE,
+ virRemoteSSHHelperEventOnSocket,
+ &proxy,
+ NULL) < 0)
+ goto cleanup;
+
+ while (!proxy.quit)
+ virEventRunDefaultImpl();
+
+ if (virGetLastErrorCode() != VIR_ERR_OK)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ if (proxy.stdinWatch != -1)
+ virEventRemoveHandle(proxy.stdinWatch);
+ if (proxy.stdoutWatch != -1)
+ virEventRemoveHandle(proxy.stdoutWatch);
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ const char *uri_str = NULL;
+ g_autoptr(virURI) uri = NULL;
+ g_autofree char *driver = NULL;
+ remoteDriverTransport transport;
+ bool user = false;
+ bool autostart = false;
+ gboolean version = false;
+ gboolean readonly = false;
+ g_autofree char *sock_path = NULL;
+ g_autofree char *daemon_name = NULL;
+ g_autoptr(virNetSocket) sock = NULL;
+ GError *error = NULL;
+ g_autoptr(GOptionContext) context = NULL;
+ GOptionEntry entries[] = {
+ { "readonly", 'r', 0, G_OPTION_ARG_NONE, &readonly, "Connect read-only", NULL },
+ { "version", 'V', 0, G_OPTION_ARG_NONE, &version, "Display version information", NULL },
+ { NULL, '\0', 0, 0, NULL, NULL, NULL }
+ };
+
+ context = g_option_context_new("- libvirt socket proxy");
+ g_option_context_add_main_entries(context, entries, PACKAGE);
+ if (!g_option_context_parse(context, &argc, &argv, &error)) {
+ g_printerr(_("option parsing failed: %s\n"), error->message);
+ exit(EXIT_FAILURE);
+ }
+
+ if (version) {
+ g_print("%s (%s) %s\n", argv[0], PACKAGE_NAME, PACKAGE_VERSION);
+ exit(EXIT_SUCCESS);
+ }
+
+ virSetErrorFunc(NULL, NULL);
+ virSetErrorLogPriorityFunc(NULL);
+
+ if (virGettextInitialize() < 0 ||
+ virErrorInitialize() < 0) {
+ g_printerr(_("%s: initialization failed\n"), argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ virFileActivateDirOverrideForProg(argv[0]);
+
+ /* Initialize the log system */
+ virLogSetFromEnv();
+
+ if (optind != (argc - 1)) {
+ g_printerr("%s: expected a URI\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ uri_str = argv[optind];
+ VIR_DEBUG("Using URI %s", uri_str);
+
+ if (!(uri = virURIParse(uri_str))) {
+ g_printerr(("%s: cannot parse '%s': %s\n"),
+ argv[0], uri_str, virGetLastErrorMessage());
+ exit(EXIT_FAILURE);
+ }
+
+ if (remoteSplitURIScheme(uri, &driver, &transport) < 0) {
+ g_printerr(_("%s: cannot parse URI transport '%s': %s\n"),
+ argv[0], uri_str, virGetLastErrorMessage());
+ exit(EXIT_FAILURE);
+ }
+
+ if (transport != REMOTE_DRIVER_TRANSPORT_UNIX) {
+ g_printerr(_("%s: unexpected URI transport '%s'\n"),
+ argv[0], uri_str);
+ exit(EXIT_FAILURE);
+ }
+
+ remoteGetURIDaemonInfo(uri, transport, &user, &autostart);
+
+ sock_path = remoteGetUNIXSocket(transport,
+ REMOTE_DRIVER_MODE_AUTO,
+ driver,
+ !!readonly,
+ user,
+ &daemon_name);
+
+ if (virNetSocketNewConnectUNIX(sock_path, autostart, daemon_name, &sock) < 0) {
+ g_printerr(_("%s: cannot connect to '%s': %s\n"),
+ argv[0], sock_path, virGetLastErrorMessage());
+ exit(EXIT_FAILURE);
+ }
+
+ if (virRemoteSSHHelperRun(sock) < 0) {
+ g_printerr(_("%s: could not proxy traffic: %s\n"),
+ argv[0], virGetLastErrorMessage());
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h
index d39b270480..3996d264fb 100644
--- a/src/rpc/virnetsocket.h
+++ b/src/rpc/virnetsocket.h
@@ -34,6 +34,7 @@
typedef struct _virNetSocket virNetSocket;
typedef virNetSocket *virNetSocketPtr;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetSocket, virObjectUnref);
typedef void (*virNetSocketIOFunc)(virNetSocketPtr sock,
int events,