1527 lines
54 KiB
Diff
1527 lines
54 KiB
Diff
From 4d4eb9e793e7722c05c697812320a14ae5797bae Mon Sep 17 00:00:00 2001
|
|
From: Pino Toscano <ptoscano@redhat.com>
|
|
Date: Mon, 8 Jul 2019 15:26:00 +0100
|
|
Subject: [PATCH 14/39] ssh: switch from libssh2 to libssh
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
RH-Author: Pino Toscano <ptoscano@redhat.com>
|
|
Message-id: <20190708152601.21123-10-ptoscano@redhat.com>
|
|
Patchwork-id: 89421
|
|
O-Subject: [RHEL-8.1.0 qemu-kvm PATCH v3 09/10] ssh: switch from libssh2 to libssh
|
|
Bugzilla: 1513367
|
|
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
|
RH-Acked-by: Max Reitz <mreitz@redhat.com>
|
|
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
RH-Acked-by: Markus Armbruster <armbru@redhat.com>
|
|
|
|
Rewrite the implementation of the ssh block driver to use libssh instead
|
|
of libssh2. The libssh library has various advantages over libssh2:
|
|
- easier API for authentication (for example for using ssh-agent)
|
|
- easier API for known_hosts handling
|
|
- supports newer types of keys in known_hosts
|
|
|
|
Use APIs/features available in libssh 0.8 conditionally, to support
|
|
older versions (which are not recommended though).
|
|
|
|
Adjust the iotest 207 according to the different error message, and to
|
|
find the default key type for localhost (to properly compare the
|
|
fingerprint with).
|
|
Contributed-by: Max Reitz <mreitz@redhat.com>
|
|
|
|
Adjust the various Docker/Travis scripts to use libssh when available
|
|
instead of libssh2. The mingw/mxe testing is dropped for now, as there
|
|
are no packages for it.
|
|
|
|
Signed-off-by: Pino Toscano <ptoscano@redhat.com>
|
|
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
|
Acked-by: Alex Bennée <alex.bennee@linaro.org>
|
|
Message-id: 20190620200840.17655-1-ptoscano@redhat.com
|
|
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
|
Message-id: 5873173.t2JhDm7DL7@lindworm.usersys.redhat.com
|
|
Signed-off-by: Max Reitz <mreitz@redhat.com>
|
|
(cherry picked from commit b10d49d7619e4957b4b971f816661b57e5061d71)
|
|
Signed-off-by: Pino Toscano <ptoscano@redhat.com>
|
|
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
|
|
---
|
|
.travis.yml | 4 +-
|
|
block/Makefile.objs | 6 +-
|
|
block/ssh.c | 652 ++++++++++++---------
|
|
block/trace-events | 14 +-
|
|
configure | 65 +-
|
|
docs/qemu-block-drivers.texi | 2 +-
|
|
tests/docker/dockerfiles/debian-win32-cross.docker | 1 -
|
|
tests/docker/dockerfiles/debian-win64-cross.docker | 1 -
|
|
tests/docker/dockerfiles/fedora.docker | 6 +-
|
|
tests/docker/dockerfiles/ubuntu.docker | 2 +-
|
|
tests/qemu-iotests/207 | 54 +-
|
|
tests/qemu-iotests/207.out | 2 +-
|
|
12 files changed, 450 insertions(+), 359 deletions(-)
|
|
|
|
diff --git a/.travis.yml b/.travis.yml
|
|
index c1e9923..d174d82 100644
|
|
--- a/.travis.yml
|
|
+++ b/.travis.yml
|
|
@@ -28,7 +28,7 @@ addons:
|
|
- libseccomp-dev
|
|
- libspice-protocol-dev
|
|
- libspice-server-dev
|
|
- - libssh2-1-dev
|
|
+ - libssh-dev
|
|
- liburcu-dev
|
|
- libusb-1.0-0-dev
|
|
- libvte-2.90-dev
|
|
@@ -188,7 +188,7 @@ matrix:
|
|
- libseccomp-dev
|
|
- libspice-protocol-dev
|
|
- libspice-server-dev
|
|
- - libssh2-1-dev
|
|
+ - libssh-dev
|
|
- liburcu-dev
|
|
- libusb-1.0-0-dev
|
|
- libvte-2.90-dev
|
|
diff --git a/block/Makefile.objs b/block/Makefile.objs
|
|
index ac7a1f8..51cd336 100644
|
|
--- a/block/Makefile.objs
|
|
+++ b/block/Makefile.objs
|
|
@@ -30,7 +30,7 @@ block-obj-$(CONFIG_CURL) += curl.o
|
|
block-obj-$(CONFIG_RBD) += rbd.o
|
|
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
|
block-obj-$(CONFIG_VXHS) += vxhs.o
|
|
-block-obj-$(CONFIG_LIBSSH2) += ssh.o
|
|
+block-obj-$(CONFIG_LIBSSH) += ssh.o
|
|
block-obj-y += accounting.o dirty-bitmap.o
|
|
block-obj-y += write-threshold.o
|
|
block-obj-y += backup.o
|
|
@@ -51,8 +51,8 @@ rbd.o-libs := $(RBD_LIBS)
|
|
gluster.o-cflags := $(GLUSTERFS_CFLAGS)
|
|
gluster.o-libs := $(GLUSTERFS_LIBS)
|
|
vxhs.o-libs := $(VXHS_LIBS)
|
|
-ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
|
-ssh.o-libs := $(LIBSSH2_LIBS)
|
|
+ssh.o-cflags := $(LIBSSH_CFLAGS)
|
|
+ssh.o-libs := $(LIBSSH_LIBS)
|
|
block-obj-dmg-bz2-$(CONFIG_BZIP2) += dmg-bz2.o
|
|
block-obj-$(if $(CONFIG_DMG),m,n) += $(block-obj-dmg-bz2-y)
|
|
dmg-bz2.o-libs := $(BZIP2_LIBS)
|
|
diff --git a/block/ssh.c b/block/ssh.c
|
|
index f0ef874..a4a374c 100644
|
|
--- a/block/ssh.c
|
|
+++ b/block/ssh.c
|
|
@@ -24,8 +24,8 @@
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
-#include <libssh2.h>
|
|
-#include <libssh2_sftp.h>
|
|
+#include <libssh/libssh.h>
|
|
+#include <libssh/sftp.h>
|
|
|
|
#include "block/block_int.h"
|
|
#include "block/qdict.h"
|
|
@@ -44,13 +44,11 @@
|
|
#include "trace.h"
|
|
|
|
/*
|
|
- * TRACE_LIBSSH2=<bitmask> enables tracing in libssh2 itself. Note
|
|
- * that this requires that libssh2 was specially compiled with the
|
|
- * `./configure --enable-debug' option, so most likely you will have
|
|
- * to compile it yourself. The meaning of <bitmask> is described
|
|
- * here: http://www.libssh2.org/libssh2_trace.html
|
|
+ * TRACE_LIBSSH=<level> enables tracing in libssh itself.
|
|
+ * The meaning of <level> is described here:
|
|
+ * http://api.libssh.org/master/group__libssh__log.html
|
|
*/
|
|
-#define TRACE_LIBSSH2 0 /* or try: LIBSSH2_TRACE_SFTP */
|
|
+#define TRACE_LIBSSH 0 /* see: SSH_LOG_* */
|
|
|
|
typedef struct BDRVSSHState {
|
|
/* Coroutine. */
|
|
@@ -58,18 +56,15 @@ typedef struct BDRVSSHState {
|
|
|
|
/* SSH connection. */
|
|
int sock; /* socket */
|
|
- LIBSSH2_SESSION *session; /* ssh session */
|
|
- LIBSSH2_SFTP *sftp; /* sftp session */
|
|
- LIBSSH2_SFTP_HANDLE *sftp_handle; /* sftp remote file handle */
|
|
+ ssh_session session; /* ssh session */
|
|
+ sftp_session sftp; /* sftp session */
|
|
+ sftp_file sftp_handle; /* sftp remote file handle */
|
|
|
|
- /* See ssh_seek() function below. */
|
|
- int64_t offset;
|
|
- bool offset_op_read;
|
|
-
|
|
- /* File attributes at open. We try to keep the .filesize field
|
|
+ /*
|
|
+ * File attributes at open. We try to keep the .size field
|
|
* updated if it changes (eg by writing at the end of the file).
|
|
*/
|
|
- LIBSSH2_SFTP_ATTRIBUTES attrs;
|
|
+ sftp_attributes attrs;
|
|
|
|
InetSocketAddress *inet;
|
|
|
|
@@ -89,7 +84,6 @@ static void ssh_state_init(BDRVSSHState *s)
|
|
{
|
|
memset(s, 0, sizeof *s);
|
|
s->sock = -1;
|
|
- s->offset = -1;
|
|
qemu_co_mutex_init(&s->lock);
|
|
}
|
|
|
|
@@ -97,20 +91,18 @@ static void ssh_state_free(BDRVSSHState *s)
|
|
{
|
|
g_free(s->user);
|
|
|
|
+ if (s->attrs) {
|
|
+ sftp_attributes_free(s->attrs);
|
|
+ }
|
|
if (s->sftp_handle) {
|
|
- libssh2_sftp_close(s->sftp_handle);
|
|
+ sftp_close(s->sftp_handle);
|
|
}
|
|
if (s->sftp) {
|
|
- libssh2_sftp_shutdown(s->sftp);
|
|
+ sftp_free(s->sftp);
|
|
}
|
|
if (s->session) {
|
|
- libssh2_session_disconnect(s->session,
|
|
- "from qemu ssh client: "
|
|
- "user closed the connection");
|
|
- libssh2_session_free(s->session);
|
|
- }
|
|
- if (s->sock >= 0) {
|
|
- close(s->sock);
|
|
+ ssh_disconnect(s->session);
|
|
+ ssh_free(s->session); /* This frees s->sock */
|
|
}
|
|
}
|
|
|
|
@@ -125,13 +117,13 @@ session_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
|
|
va_end(args);
|
|
|
|
if (s->session) {
|
|
- char *ssh_err;
|
|
+ const char *ssh_err;
|
|
int ssh_err_code;
|
|
|
|
- /* This is not an errno. See <libssh2.h>. */
|
|
- ssh_err_code = libssh2_session_last_error(s->session,
|
|
- &ssh_err, NULL, 0);
|
|
- error_setg(errp, "%s: %s (libssh2 error code: %d)",
|
|
+ /* This is not an errno. See <libssh/libssh.h>. */
|
|
+ ssh_err = ssh_get_error(s->session);
|
|
+ ssh_err_code = ssh_get_error_code(s->session);
|
|
+ error_setg(errp, "%s: %s (libssh error code: %d)",
|
|
msg, ssh_err, ssh_err_code);
|
|
} else {
|
|
error_setg(errp, "%s", msg);
|
|
@@ -150,18 +142,18 @@ sftp_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
|
|
va_end(args);
|
|
|
|
if (s->sftp) {
|
|
- char *ssh_err;
|
|
+ const char *ssh_err;
|
|
int ssh_err_code;
|
|
- unsigned long sftp_err_code;
|
|
+ int sftp_err_code;
|
|
|
|
- /* This is not an errno. See <libssh2.h>. */
|
|
- ssh_err_code = libssh2_session_last_error(s->session,
|
|
- &ssh_err, NULL, 0);
|
|
- /* See <libssh2_sftp.h>. */
|
|
- sftp_err_code = libssh2_sftp_last_error((s)->sftp);
|
|
+ /* This is not an errno. See <libssh/libssh.h>. */
|
|
+ ssh_err = ssh_get_error(s->session);
|
|
+ ssh_err_code = ssh_get_error_code(s->session);
|
|
+ /* See <libssh/sftp.h>. */
|
|
+ sftp_err_code = sftp_get_error(s->sftp);
|
|
|
|
error_setg(errp,
|
|
- "%s: %s (libssh2 error code: %d, sftp error code: %lu)",
|
|
+ "%s: %s (libssh error code: %d, sftp error code: %d)",
|
|
msg, ssh_err, ssh_err_code, sftp_err_code);
|
|
} else {
|
|
error_setg(errp, "%s", msg);
|
|
@@ -171,15 +163,15 @@ sftp_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
|
|
|
|
static void sftp_error_trace(BDRVSSHState *s, const char *op)
|
|
{
|
|
- char *ssh_err;
|
|
+ const char *ssh_err;
|
|
int ssh_err_code;
|
|
- unsigned long sftp_err_code;
|
|
+ int sftp_err_code;
|
|
|
|
- /* This is not an errno. See <libssh2.h>. */
|
|
- ssh_err_code = libssh2_session_last_error(s->session,
|
|
- &ssh_err, NULL, 0);
|
|
- /* See <libssh2_sftp.h>. */
|
|
- sftp_err_code = libssh2_sftp_last_error((s)->sftp);
|
|
+ /* This is not an errno. See <libssh/libssh.h>. */
|
|
+ ssh_err = ssh_get_error(s->session);
|
|
+ ssh_err_code = ssh_get_error_code(s->session);
|
|
+ /* See <libssh/sftp.h>. */
|
|
+ sftp_err_code = sftp_get_error(s->sftp);
|
|
|
|
trace_sftp_error(op, ssh_err, ssh_err_code, sftp_err_code);
|
|
}
|
|
@@ -280,82 +272,120 @@ static void ssh_parse_filename(const char *filename, QDict *options,
|
|
parse_uri(filename, options, errp);
|
|
}
|
|
|
|
-static int check_host_key_knownhosts(BDRVSSHState *s,
|
|
- const char *host, int port, Error **errp)
|
|
+static int check_host_key_knownhosts(BDRVSSHState *s, Error **errp)
|
|
{
|
|
- const char *home;
|
|
- char *knh_file = NULL;
|
|
- LIBSSH2_KNOWNHOSTS *knh = NULL;
|
|
- struct libssh2_knownhost *found;
|
|
- int ret, r;
|
|
- const char *hostkey;
|
|
- size_t len;
|
|
- int type;
|
|
-
|
|
- hostkey = libssh2_session_hostkey(s->session, &len, &type);
|
|
- if (!hostkey) {
|
|
+ int ret;
|
|
+#ifdef HAVE_LIBSSH_0_8
|
|
+ enum ssh_known_hosts_e state;
|
|
+ int r;
|
|
+ ssh_key pubkey;
|
|
+ enum ssh_keytypes_e pubkey_type;
|
|
+ unsigned char *server_hash = NULL;
|
|
+ size_t server_hash_len;
|
|
+ char *fingerprint = NULL;
|
|
+
|
|
+ state = ssh_session_is_known_server(s->session);
|
|
+ trace_ssh_server_status(state);
|
|
+
|
|
+ switch (state) {
|
|
+ case SSH_KNOWN_HOSTS_OK:
|
|
+ /* OK */
|
|
+ trace_ssh_check_host_key_knownhosts();
|
|
+ break;
|
|
+ case SSH_KNOWN_HOSTS_CHANGED:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s, "failed to read remote host key");
|
|
+ r = ssh_get_server_publickey(s->session, &pubkey);
|
|
+ if (r == 0) {
|
|
+ r = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256,
|
|
+ &server_hash, &server_hash_len);
|
|
+ pubkey_type = ssh_key_type(pubkey);
|
|
+ ssh_key_free(pubkey);
|
|
+ }
|
|
+ if (r == 0) {
|
|
+ fingerprint = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256,
|
|
+ server_hash,
|
|
+ server_hash_len);
|
|
+ ssh_clean_pubkey_hash(&server_hash);
|
|
+ }
|
|
+ if (fingerprint) {
|
|
+ error_setg(errp,
|
|
+ "host key (%s key with fingerprint %s) does not match "
|
|
+ "the one in known_hosts; this may be a possible attack",
|
|
+ ssh_key_type_to_char(pubkey_type), fingerprint);
|
|
+ ssh_string_free_char(fingerprint);
|
|
+ } else {
|
|
+ error_setg(errp,
|
|
+ "host key does not match the one in known_hosts; this "
|
|
+ "may be a possible attack");
|
|
+ }
|
|
goto out;
|
|
- }
|
|
-
|
|
- knh = libssh2_knownhost_init(s->session);
|
|
- if (!knh) {
|
|
+ case SSH_KNOWN_HOSTS_OTHER:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s,
|
|
- "failed to initialize known hosts support");
|
|
+ error_setg(errp,
|
|
+ "host key for this server not found, another type exists");
|
|
+ goto out;
|
|
+ case SSH_KNOWN_HOSTS_UNKNOWN:
|
|
+ ret = -EINVAL;
|
|
+ error_setg(errp, "no host key was found in known_hosts");
|
|
+ goto out;
|
|
+ case SSH_KNOWN_HOSTS_NOT_FOUND:
|
|
+ ret = -ENOENT;
|
|
+ error_setg(errp, "known_hosts file not found");
|
|
+ goto out;
|
|
+ case SSH_KNOWN_HOSTS_ERROR:
|
|
+ ret = -EINVAL;
|
|
+ error_setg(errp, "error while checking the host");
|
|
+ goto out;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ error_setg(errp, "error while checking for known server (%d)", state);
|
|
goto out;
|
|
}
|
|
+#else /* !HAVE_LIBSSH_0_8 */
|
|
+ int state;
|
|
|
|
- home = getenv("HOME");
|
|
- if (home) {
|
|
- knh_file = g_strdup_printf("%s/.ssh/known_hosts", home);
|
|
- } else {
|
|
- knh_file = g_strdup_printf("/root/.ssh/known_hosts");
|
|
- }
|
|
-
|
|
- /* Read all known hosts from OpenSSH-style known_hosts file. */
|
|
- libssh2_knownhost_readfile(knh, knh_file, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
|
|
+ state = ssh_is_server_known(s->session);
|
|
+ trace_ssh_server_status(state);
|
|
|
|
- r = libssh2_knownhost_checkp(knh, host, port, hostkey, len,
|
|
- LIBSSH2_KNOWNHOST_TYPE_PLAIN|
|
|
- LIBSSH2_KNOWNHOST_KEYENC_RAW,
|
|
- &found);
|
|
- switch (r) {
|
|
- case LIBSSH2_KNOWNHOST_CHECK_MATCH:
|
|
+ switch (state) {
|
|
+ case SSH_SERVER_KNOWN_OK:
|
|
/* OK */
|
|
- trace_ssh_check_host_key_knownhosts(found->key);
|
|
+ trace_ssh_check_host_key_knownhosts();
|
|
break;
|
|
- case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
|
|
+ case SSH_SERVER_KNOWN_CHANGED:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s,
|
|
- "host key does not match the one in known_hosts"
|
|
- " (found key %s)", found->key);
|
|
+ error_setg(errp,
|
|
+ "host key does not match the one in known_hosts; this "
|
|
+ "may be a possible attack");
|
|
goto out;
|
|
- case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
|
|
+ case SSH_SERVER_FOUND_OTHER:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s, "no host key was found in known_hosts");
|
|
+ error_setg(errp,
|
|
+ "host key for this server not found, another type exists");
|
|
goto out;
|
|
- case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
|
|
+ case SSH_SERVER_FILE_NOT_FOUND:
|
|
+ ret = -ENOENT;
|
|
+ error_setg(errp, "known_hosts file not found");
|
|
+ goto out;
|
|
+ case SSH_SERVER_NOT_KNOWN:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s,
|
|
- "failure matching the host key with known_hosts");
|
|
+ error_setg(errp, "no host key was found in known_hosts");
|
|
+ goto out;
|
|
+ case SSH_SERVER_ERROR:
|
|
+ ret = -EINVAL;
|
|
+ error_setg(errp, "server error");
|
|
goto out;
|
|
default:
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s, "unknown error matching the host key"
|
|
- " with known_hosts (%d)", r);
|
|
+ error_setg(errp, "error while checking for known server (%d)", state);
|
|
goto out;
|
|
}
|
|
+#endif /* !HAVE_LIBSSH_0_8 */
|
|
|
|
/* known_hosts checking successful. */
|
|
ret = 0;
|
|
|
|
out:
|
|
- if (knh != NULL) {
|
|
- libssh2_knownhost_free(knh);
|
|
- }
|
|
- g_free(knh_file);
|
|
return ret;
|
|
}
|
|
|
|
@@ -399,18 +429,34 @@ static int compare_fingerprint(const unsigned char *fingerprint, size_t len,
|
|
|
|
static int
|
|
check_host_key_hash(BDRVSSHState *s, const char *hash,
|
|
- int hash_type, size_t fingerprint_len, Error **errp)
|
|
+ enum ssh_publickey_hash_type type, Error **errp)
|
|
{
|
|
- const char *fingerprint;
|
|
-
|
|
- fingerprint = libssh2_hostkey_hash(s->session, hash_type);
|
|
- if (!fingerprint) {
|
|
+ int r;
|
|
+ ssh_key pubkey;
|
|
+ unsigned char *server_hash;
|
|
+ size_t server_hash_len;
|
|
+
|
|
+#ifdef HAVE_LIBSSH_0_8
|
|
+ r = ssh_get_server_publickey(s->session, &pubkey);
|
|
+#else
|
|
+ r = ssh_get_publickey(s->session, &pubkey);
|
|
+#endif
|
|
+ if (r != SSH_OK) {
|
|
session_error_setg(errp, s, "failed to read remote host key");
|
|
return -EINVAL;
|
|
}
|
|
|
|
- if(compare_fingerprint((unsigned char *) fingerprint, fingerprint_len,
|
|
- hash) != 0) {
|
|
+ r = ssh_get_publickey_hash(pubkey, type, &server_hash, &server_hash_len);
|
|
+ ssh_key_free(pubkey);
|
|
+ if (r != 0) {
|
|
+ session_error_setg(errp, s,
|
|
+ "failed reading the hash of the server SSH key");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ r = compare_fingerprint(server_hash, server_hash_len, hash);
|
|
+ ssh_clean_pubkey_hash(&server_hash);
|
|
+ if (r != 0) {
|
|
error_setg(errp, "remote host key does not match host_key_check '%s'",
|
|
hash);
|
|
return -EPERM;
|
|
@@ -419,8 +465,7 @@ check_host_key_hash(BDRVSSHState *s, const char *hash,
|
|
return 0;
|
|
}
|
|
|
|
-static int check_host_key(BDRVSSHState *s, const char *host, int port,
|
|
- SshHostKeyCheck *hkc, Error **errp)
|
|
+static int check_host_key(BDRVSSHState *s, SshHostKeyCheck *hkc, Error **errp)
|
|
{
|
|
SshHostKeyCheckMode mode;
|
|
|
|
@@ -436,15 +481,15 @@ static int check_host_key(BDRVSSHState *s, const char *host, int port,
|
|
case SSH_HOST_KEY_CHECK_MODE_HASH:
|
|
if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
|
|
return check_host_key_hash(s, hkc->u.hash.hash,
|
|
- LIBSSH2_HOSTKEY_HASH_MD5, 16, errp);
|
|
+ SSH_PUBLICKEY_HASH_MD5, errp);
|
|
} else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
|
|
return check_host_key_hash(s, hkc->u.hash.hash,
|
|
- LIBSSH2_HOSTKEY_HASH_SHA1, 20, errp);
|
|
+ SSH_PUBLICKEY_HASH_SHA1, errp);
|
|
}
|
|
g_assert_not_reached();
|
|
break;
|
|
case SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS:
|
|
- return check_host_key_knownhosts(s, host, port, errp);
|
|
+ return check_host_key_knownhosts(s, errp);
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
@@ -452,60 +497,43 @@ static int check_host_key(BDRVSSHState *s, const char *host, int port,
|
|
return -EINVAL;
|
|
}
|
|
|
|
-static int authenticate(BDRVSSHState *s, const char *user, Error **errp)
|
|
+static int authenticate(BDRVSSHState *s, Error **errp)
|
|
{
|
|
int r, ret;
|
|
- const char *userauthlist;
|
|
- LIBSSH2_AGENT *agent = NULL;
|
|
- struct libssh2_agent_publickey *identity;
|
|
- struct libssh2_agent_publickey *prev_identity = NULL;
|
|
+ int method;
|
|
|
|
- userauthlist = libssh2_userauth_list(s->session, user, strlen(user));
|
|
- if (strstr(userauthlist, "publickey") == NULL) {
|
|
+ /* Try to authenticate with the "none" method. */
|
|
+ r = ssh_userauth_none(s->session, NULL);
|
|
+ if (r == SSH_AUTH_ERROR) {
|
|
ret = -EPERM;
|
|
- error_setg(errp,
|
|
- "remote server does not support \"publickey\" authentication");
|
|
+ session_error_setg(errp, s, "failed to authenticate using none "
|
|
+ "authentication");
|
|
goto out;
|
|
- }
|
|
-
|
|
- /* Connect to ssh-agent and try each identity in turn. */
|
|
- agent = libssh2_agent_init(s->session);
|
|
- if (!agent) {
|
|
- ret = -EINVAL;
|
|
- session_error_setg(errp, s, "failed to initialize ssh-agent support");
|
|
- goto out;
|
|
- }
|
|
- if (libssh2_agent_connect(agent)) {
|
|
- ret = -ECONNREFUSED;
|
|
- session_error_setg(errp, s, "failed to connect to ssh-agent");
|
|
- goto out;
|
|
- }
|
|
- if (libssh2_agent_list_identities(agent)) {
|
|
- ret = -EINVAL;
|
|
- session_error_setg(errp, s,
|
|
- "failed requesting identities from ssh-agent");
|
|
+ } else if (r == SSH_AUTH_SUCCESS) {
|
|
+ /* Authenticated! */
|
|
+ ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
- for(;;) {
|
|
- r = libssh2_agent_get_identity(agent, &identity, prev_identity);
|
|
- if (r == 1) { /* end of list */
|
|
- break;
|
|
- }
|
|
- if (r < 0) {
|
|
+ method = ssh_userauth_list(s->session, NULL);
|
|
+ trace_ssh_auth_methods(method);
|
|
+
|
|
+ /*
|
|
+ * Try to authenticate with publickey, using the ssh-agent
|
|
+ * if available.
|
|
+ */
|
|
+ if (method & SSH_AUTH_METHOD_PUBLICKEY) {
|
|
+ r = ssh_userauth_publickey_auto(s->session, NULL, NULL);
|
|
+ if (r == SSH_AUTH_ERROR) {
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s,
|
|
- "failed to obtain identity from ssh-agent");
|
|
+ session_error_setg(errp, s, "failed to authenticate using "
|
|
+ "publickey authentication");
|
|
goto out;
|
|
- }
|
|
- r = libssh2_agent_userauth(agent, user, identity);
|
|
- if (r == 0) {
|
|
+ } else if (r == SSH_AUTH_SUCCESS) {
|
|
/* Authenticated! */
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
- /* Failed to authenticate with this identity, try the next one. */
|
|
- prev_identity = identity;
|
|
}
|
|
|
|
ret = -EPERM;
|
|
@@ -513,13 +541,6 @@ static int authenticate(BDRVSSHState *s, const char *user, Error **errp)
|
|
"and the identities held by your ssh-agent");
|
|
|
|
out:
|
|
- if (agent != NULL) {
|
|
- /* Note: libssh2 implementation implicitly calls
|
|
- * libssh2_agent_disconnect if necessary.
|
|
- */
|
|
- libssh2_agent_free(agent);
|
|
- }
|
|
-
|
|
return ret;
|
|
}
|
|
|
|
@@ -638,7 +659,8 @@ static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
|
|
int ssh_flags, int creat_mode, Error **errp)
|
|
{
|
|
int r, ret;
|
|
- long port = 0;
|
|
+ unsigned int port = 0;
|
|
+ int new_sock = -1;
|
|
|
|
if (opts->has_user) {
|
|
s->user = g_strdup(opts->user);
|
|
@@ -655,71 +677,147 @@ static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
|
|
s->inet = opts->server;
|
|
opts->server = NULL;
|
|
|
|
- if (qemu_strtol(s->inet->port, NULL, 10, &port) < 0) {
|
|
+ if (qemu_strtoui(s->inet->port, NULL, 10, &port) < 0) {
|
|
error_setg(errp, "Use only numeric port value");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/* Open the socket and connect. */
|
|
- s->sock = inet_connect_saddr(s->inet, errp);
|
|
- if (s->sock < 0) {
|
|
+ new_sock = inet_connect_saddr(s->inet, errp);
|
|
+ if (new_sock < 0) {
|
|
ret = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
+ /*
|
|
+ * Try to disable the Nagle algorithm on TCP sockets to reduce latency,
|
|
+ * but do not fail if it cannot be disabled.
|
|
+ */
|
|
+ r = socket_set_nodelay(new_sock);
|
|
+ if (r < 0) {
|
|
+ warn_report("can't set TCP_NODELAY for the ssh server %s: %s",
|
|
+ s->inet->host, strerror(errno));
|
|
+ }
|
|
+
|
|
/* Create SSH session. */
|
|
- s->session = libssh2_session_init();
|
|
+ s->session = ssh_new();
|
|
if (!s->session) {
|
|
ret = -EINVAL;
|
|
- session_error_setg(errp, s, "failed to initialize libssh2 session");
|
|
+ session_error_setg(errp, s, "failed to initialize libssh session");
|
|
goto err;
|
|
}
|
|
|
|
-#if TRACE_LIBSSH2 != 0
|
|
- libssh2_trace(s->session, TRACE_LIBSSH2);
|
|
-#endif
|
|
+ /*
|
|
+ * Make sure we are in blocking mode during the connection and
|
|
+ * authentication phases.
|
|
+ */
|
|
+ ssh_set_blocking(s->session, 1);
|
|
|
|
- r = libssh2_session_handshake(s->session, s->sock);
|
|
- if (r != 0) {
|
|
+ r = ssh_options_set(s->session, SSH_OPTIONS_USER, s->user);
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s,
|
|
+ "failed to set the user in the libssh session");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ r = ssh_options_set(s->session, SSH_OPTIONS_HOST, s->inet->host);
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s,
|
|
+ "failed to set the host in the libssh session");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ if (port > 0) {
|
|
+ r = ssh_options_set(s->session, SSH_OPTIONS_PORT, &port);
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s,
|
|
+ "failed to set the port in the libssh session");
|
|
+ goto err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ r = ssh_options_set(s->session, SSH_OPTIONS_COMPRESSION, "none");
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s,
|
|
+ "failed to disable the compression in the libssh "
|
|
+ "session");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ /* Read ~/.ssh/config. */
|
|
+ r = ssh_options_parse_config(s->session, NULL);
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s, "failed to parse ~/.ssh/config");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ r = ssh_options_set(s->session, SSH_OPTIONS_FD, &new_sock);
|
|
+ if (r < 0) {
|
|
+ ret = -EINVAL;
|
|
+ session_error_setg(errp, s,
|
|
+ "failed to set the socket in the libssh session");
|
|
+ goto err;
|
|
+ }
|
|
+ /* libssh took ownership of the socket. */
|
|
+ s->sock = new_sock;
|
|
+ new_sock = -1;
|
|
+
|
|
+ /* Connect. */
|
|
+ r = ssh_connect(s->session);
|
|
+ if (r != SSH_OK) {
|
|
ret = -EINVAL;
|
|
session_error_setg(errp, s, "failed to establish SSH session");
|
|
goto err;
|
|
}
|
|
|
|
/* Check the remote host's key against known_hosts. */
|
|
- ret = check_host_key(s, s->inet->host, port, opts->host_key_check, errp);
|
|
+ ret = check_host_key(s, opts->host_key_check, errp);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
|
|
/* Authenticate. */
|
|
- ret = authenticate(s, s->user, errp);
|
|
+ ret = authenticate(s, errp);
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
|
|
/* Start SFTP. */
|
|
- s->sftp = libssh2_sftp_init(s->session);
|
|
+ s->sftp = sftp_new(s->session);
|
|
if (!s->sftp) {
|
|
- session_error_setg(errp, s, "failed to initialize sftp handle");
|
|
+ session_error_setg(errp, s, "failed to create sftp handle");
|
|
+ ret = -EINVAL;
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ r = sftp_init(s->sftp);
|
|
+ if (r < 0) {
|
|
+ sftp_error_setg(errp, s, "failed to initialize sftp handle");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/* Open the remote file. */
|
|
trace_ssh_connect_to_ssh(opts->path, ssh_flags, creat_mode);
|
|
- s->sftp_handle = libssh2_sftp_open(s->sftp, opts->path, ssh_flags,
|
|
- creat_mode);
|
|
+ s->sftp_handle = sftp_open(s->sftp, opts->path, ssh_flags, creat_mode);
|
|
if (!s->sftp_handle) {
|
|
- session_error_setg(errp, s, "failed to open remote file '%s'",
|
|
- opts->path);
|
|
+ sftp_error_setg(errp, s, "failed to open remote file '%s'",
|
|
+ opts->path);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
- r = libssh2_sftp_fstat(s->sftp_handle, &s->attrs);
|
|
- if (r < 0) {
|
|
+ /* Make sure the SFTP file is handled in blocking mode. */
|
|
+ sftp_file_set_blocking(s->sftp_handle);
|
|
+
|
|
+ s->attrs = sftp_fstat(s->sftp_handle);
|
|
+ if (!s->attrs) {
|
|
sftp_error_setg(errp, s, "failed to read file attributes");
|
|
return -EINVAL;
|
|
}
|
|
@@ -727,21 +825,27 @@ static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
|
|
return 0;
|
|
|
|
err:
|
|
+ if (s->attrs) {
|
|
+ sftp_attributes_free(s->attrs);
|
|
+ }
|
|
+ s->attrs = NULL;
|
|
if (s->sftp_handle) {
|
|
- libssh2_sftp_close(s->sftp_handle);
|
|
+ sftp_close(s->sftp_handle);
|
|
}
|
|
s->sftp_handle = NULL;
|
|
if (s->sftp) {
|
|
- libssh2_sftp_shutdown(s->sftp);
|
|
+ sftp_free(s->sftp);
|
|
}
|
|
s->sftp = NULL;
|
|
if (s->session) {
|
|
- libssh2_session_disconnect(s->session,
|
|
- "from qemu ssh client: "
|
|
- "error opening connection");
|
|
- libssh2_session_free(s->session);
|
|
+ ssh_disconnect(s->session);
|
|
+ ssh_free(s->session);
|
|
}
|
|
s->session = NULL;
|
|
+ s->sock = -1;
|
|
+ if (new_sock >= 0) {
|
|
+ close(new_sock);
|
|
+ }
|
|
|
|
return ret;
|
|
}
|
|
@@ -756,9 +860,11 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|
|
|
ssh_state_init(s);
|
|
|
|
- ssh_flags = LIBSSH2_FXF_READ;
|
|
+ ssh_flags = 0;
|
|
if (bdrv_flags & BDRV_O_RDWR) {
|
|
- ssh_flags |= LIBSSH2_FXF_WRITE;
|
|
+ ssh_flags |= O_RDWR;
|
|
+ } else {
|
|
+ ssh_flags |= O_RDONLY;
|
|
}
|
|
|
|
opts = ssh_parse_options(options, errp);
|
|
@@ -773,18 +879,13 @@ static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
|
|
}
|
|
|
|
/* Go non-blocking. */
|
|
- libssh2_session_set_blocking(s->session, 0);
|
|
+ ssh_set_blocking(s->session, 0);
|
|
|
|
qapi_free_BlockdevOptionsSsh(opts);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
- if (s->sock >= 0) {
|
|
- close(s->sock);
|
|
- }
|
|
- s->sock = -1;
|
|
-
|
|
qapi_free_BlockdevOptionsSsh(opts);
|
|
|
|
return ret;
|
|
@@ -795,25 +896,25 @@ static int ssh_grow_file(BDRVSSHState *s, int64_t offset, Error **errp)
|
|
{
|
|
ssize_t ret;
|
|
char c[1] = { '\0' };
|
|
- int was_blocking = libssh2_session_get_blocking(s->session);
|
|
+ int was_blocking = ssh_is_blocking(s->session);
|
|
|
|
/* offset must be strictly greater than the current size so we do
|
|
* not overwrite anything */
|
|
- assert(offset > 0 && offset > s->attrs.filesize);
|
|
+ assert(offset > 0 && offset > s->attrs->size);
|
|
|
|
- libssh2_session_set_blocking(s->session, 1);
|
|
+ ssh_set_blocking(s->session, 1);
|
|
|
|
- libssh2_sftp_seek64(s->sftp_handle, offset - 1);
|
|
- ret = libssh2_sftp_write(s->sftp_handle, c, 1);
|
|
+ sftp_seek64(s->sftp_handle, offset - 1);
|
|
+ ret = sftp_write(s->sftp_handle, c, 1);
|
|
|
|
- libssh2_session_set_blocking(s->session, was_blocking);
|
|
+ ssh_set_blocking(s->session, was_blocking);
|
|
|
|
if (ret < 0) {
|
|
sftp_error_setg(errp, s, "Failed to grow file");
|
|
return -EIO;
|
|
}
|
|
|
|
- s->attrs.filesize = offset;
|
|
+ s->attrs->size = offset;
|
|
return 0;
|
|
}
|
|
|
|
@@ -841,8 +942,7 @@ static int ssh_co_create(BlockdevCreateOptions *options, Error **errp)
|
|
ssh_state_init(&s);
|
|
|
|
ret = connect_to_ssh(&s, opts->location,
|
|
- LIBSSH2_FXF_READ|LIBSSH2_FXF_WRITE|
|
|
- LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
|
|
+ O_RDWR | O_CREAT | O_TRUNC,
|
|
0644, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
@@ -911,10 +1011,8 @@ static int ssh_has_zero_init(BlockDriverState *bs)
|
|
/* Assume false, unless we can positively prove it's true. */
|
|
int has_zero_init = 0;
|
|
|
|
- if (s->attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
|
|
- if (s->attrs.permissions & LIBSSH2_SFTP_S_IFREG) {
|
|
- has_zero_init = 1;
|
|
- }
|
|
+ if (s->attrs->type == SSH_FILEXFER_TYPE_REGULAR) {
|
|
+ has_zero_init = 1;
|
|
}
|
|
|
|
return has_zero_init;
|
|
@@ -951,12 +1049,12 @@ static coroutine_fn void co_yield(BDRVSSHState *s, BlockDriverState *bs)
|
|
.co = qemu_coroutine_self()
|
|
};
|
|
|
|
- r = libssh2_session_block_directions(s->session);
|
|
+ r = ssh_get_poll_flags(s->session);
|
|
|
|
- if (r & LIBSSH2_SESSION_BLOCK_INBOUND) {
|
|
+ if (r & SSH_READ_PENDING) {
|
|
rd_handler = restart_coroutine;
|
|
}
|
|
- if (r & LIBSSH2_SESSION_BLOCK_OUTBOUND) {
|
|
+ if (r & SSH_WRITE_PENDING) {
|
|
wr_handler = restart_coroutine;
|
|
}
|
|
|
|
@@ -968,33 +1066,6 @@ static coroutine_fn void co_yield(BDRVSSHState *s, BlockDriverState *bs)
|
|
trace_ssh_co_yield_back(s->sock);
|
|
}
|
|
|
|
-/* SFTP has a function `libssh2_sftp_seek64' which seeks to a position
|
|
- * in the remote file. Notice that it just updates a field in the
|
|
- * sftp_handle structure, so there is no network traffic and it cannot
|
|
- * fail.
|
|
- *
|
|
- * However, `libssh2_sftp_seek64' does have a catastrophic effect on
|
|
- * performance since it causes the handle to throw away all in-flight
|
|
- * reads and buffered readahead data. Therefore this function tries
|
|
- * to be intelligent about when to call the underlying libssh2 function.
|
|
- */
|
|
-#define SSH_SEEK_WRITE 0
|
|
-#define SSH_SEEK_READ 1
|
|
-#define SSH_SEEK_FORCE 2
|
|
-
|
|
-static void ssh_seek(BDRVSSHState *s, int64_t offset, int flags)
|
|
-{
|
|
- bool op_read = (flags & SSH_SEEK_READ) != 0;
|
|
- bool force = (flags & SSH_SEEK_FORCE) != 0;
|
|
-
|
|
- if (force || op_read != s->offset_op_read || offset != s->offset) {
|
|
- trace_ssh_seek(offset);
|
|
- libssh2_sftp_seek64(s->sftp_handle, offset);
|
|
- s->offset = offset;
|
|
- s->offset_op_read = op_read;
|
|
- }
|
|
-}
|
|
-
|
|
static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
|
|
int64_t offset, size_t size,
|
|
QEMUIOVector *qiov)
|
|
@@ -1006,7 +1077,8 @@ static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
|
|
|
|
trace_ssh_read(offset, size);
|
|
|
|
- ssh_seek(s, offset, SSH_SEEK_READ);
|
|
+ trace_ssh_seek(offset);
|
|
+ sftp_seek64(s->sftp_handle, offset);
|
|
|
|
/* This keeps track of the current iovec element ('i'), where we
|
|
* will write to next ('buf'), and the end of the current iovec
|
|
@@ -1016,35 +1088,35 @@ static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
|
|
buf = i->iov_base;
|
|
end_of_vec = i->iov_base + i->iov_len;
|
|
|
|
- /* libssh2 has a hard-coded limit of 2000 bytes per request,
|
|
- * although it will also do readahead behind our backs. Therefore
|
|
- * we may have to do repeated reads here until we have read 'size'
|
|
- * bytes.
|
|
- */
|
|
for (got = 0; got < size; ) {
|
|
+ size_t request_read_size;
|
|
again:
|
|
- trace_ssh_read_buf(buf, end_of_vec - buf);
|
|
- r = libssh2_sftp_read(s->sftp_handle, buf, end_of_vec - buf);
|
|
- trace_ssh_read_return(r);
|
|
+ /*
|
|
+ * The size of SFTP packets is limited to 32K bytes, so limit
|
|
+ * the amount of data requested to 16K, as libssh currently
|
|
+ * does not handle multiple requests on its own.
|
|
+ */
|
|
+ request_read_size = MIN(end_of_vec - buf, 16384);
|
|
+ trace_ssh_read_buf(buf, end_of_vec - buf, request_read_size);
|
|
+ r = sftp_read(s->sftp_handle, buf, request_read_size);
|
|
+ trace_ssh_read_return(r, sftp_get_error(s->sftp));
|
|
|
|
- if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
|
|
+ if (r == SSH_AGAIN) {
|
|
co_yield(s, bs);
|
|
goto again;
|
|
}
|
|
- if (r < 0) {
|
|
- sftp_error_trace(s, "read");
|
|
- s->offset = -1;
|
|
- return -EIO;
|
|
- }
|
|
- if (r == 0) {
|
|
+ if (r == SSH_EOF || (r == 0 && sftp_get_error(s->sftp) == SSH_FX_EOF)) {
|
|
/* EOF: Short read so pad the buffer with zeroes and return it. */
|
|
qemu_iovec_memset(qiov, got, 0, size - got);
|
|
return 0;
|
|
}
|
|
+ if (r <= 0) {
|
|
+ sftp_error_trace(s, "read");
|
|
+ return -EIO;
|
|
+ }
|
|
|
|
got += r;
|
|
buf += r;
|
|
- s->offset += r;
|
|
if (buf >= end_of_vec && got < size) {
|
|
i++;
|
|
buf = i->iov_base;
|
|
@@ -1081,7 +1153,8 @@ static int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
|
|
|
|
trace_ssh_write(offset, size);
|
|
|
|
- ssh_seek(s, offset, SSH_SEEK_WRITE);
|
|
+ trace_ssh_seek(offset);
|
|
+ sftp_seek64(s->sftp_handle, offset);
|
|
|
|
/* This keeps track of the current iovec element ('i'), where we
|
|
* will read from next ('buf'), and the end of the current iovec
|
|
@@ -1092,46 +1165,37 @@ static int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
|
|
end_of_vec = i->iov_base + i->iov_len;
|
|
|
|
for (written = 0; written < size; ) {
|
|
+ size_t request_write_size;
|
|
again:
|
|
- trace_ssh_write_buf(buf, end_of_vec - buf);
|
|
- r = libssh2_sftp_write(s->sftp_handle, buf, end_of_vec - buf);
|
|
- trace_ssh_write_return(r);
|
|
+ /*
|
|
+ * Avoid too large data packets, as libssh currently does not
|
|
+ * handle multiple requests on its own.
|
|
+ */
|
|
+ request_write_size = MIN(end_of_vec - buf, 131072);
|
|
+ trace_ssh_write_buf(buf, end_of_vec - buf, request_write_size);
|
|
+ r = sftp_write(s->sftp_handle, buf, request_write_size);
|
|
+ trace_ssh_write_return(r, sftp_get_error(s->sftp));
|
|
|
|
- if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
|
|
+ if (r == SSH_AGAIN) {
|
|
co_yield(s, bs);
|
|
goto again;
|
|
}
|
|
if (r < 0) {
|
|
sftp_error_trace(s, "write");
|
|
- s->offset = -1;
|
|
return -EIO;
|
|
}
|
|
- /* The libssh2 API is very unclear about this. A comment in
|
|
- * the code says "nothing was acked, and no EAGAIN was
|
|
- * received!" which apparently means that no data got sent
|
|
- * out, and the underlying channel didn't return any EAGAIN
|
|
- * indication. I think this is a bug in either libssh2 or
|
|
- * OpenSSH (server-side). In any case, forcing a seek (to
|
|
- * discard libssh2 internal buffers), and then trying again
|
|
- * works for me.
|
|
- */
|
|
- if (r == 0) {
|
|
- ssh_seek(s, offset + written, SSH_SEEK_WRITE|SSH_SEEK_FORCE);
|
|
- co_yield(s, bs);
|
|
- goto again;
|
|
- }
|
|
|
|
written += r;
|
|
buf += r;
|
|
- s->offset += r;
|
|
if (buf >= end_of_vec && written < size) {
|
|
i++;
|
|
buf = i->iov_base;
|
|
end_of_vec = i->iov_base + i->iov_len;
|
|
}
|
|
|
|
- if (offset + written > s->attrs.filesize)
|
|
- s->attrs.filesize = offset + written;
|
|
+ if (offset + written > s->attrs->size) {
|
|
+ s->attrs->size = offset + written;
|
|
+ }
|
|
}
|
|
|
|
return 0;
|
|
@@ -1164,24 +1228,24 @@ static void unsafe_flush_warning(BDRVSSHState *s, const char *what)
|
|
}
|
|
}
|
|
|
|
-#ifdef HAS_LIBSSH2_SFTP_FSYNC
|
|
+#ifdef HAVE_LIBSSH_0_8
|
|
|
|
static coroutine_fn int ssh_flush(BDRVSSHState *s, BlockDriverState *bs)
|
|
{
|
|
int r;
|
|
|
|
trace_ssh_flush();
|
|
+
|
|
+ if (!sftp_extension_supported(s->sftp, "fsync@openssh.com", "1")) {
|
|
+ unsafe_flush_warning(s, "OpenSSH >= 6.3");
|
|
+ return 0;
|
|
+ }
|
|
again:
|
|
- r = libssh2_sftp_fsync(s->sftp_handle);
|
|
- if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
|
|
+ r = sftp_fsync(s->sftp_handle);
|
|
+ if (r == SSH_AGAIN) {
|
|
co_yield(s, bs);
|
|
goto again;
|
|
}
|
|
- if (r == LIBSSH2_ERROR_SFTP_PROTOCOL &&
|
|
- libssh2_sftp_last_error(s->sftp) == LIBSSH2_FX_OP_UNSUPPORTED) {
|
|
- unsafe_flush_warning(s, "OpenSSH >= 6.3");
|
|
- return 0;
|
|
- }
|
|
if (r < 0) {
|
|
sftp_error_trace(s, "fsync");
|
|
return -EIO;
|
|
@@ -1202,25 +1266,25 @@ static coroutine_fn int ssh_co_flush(BlockDriverState *bs)
|
|
return ret;
|
|
}
|
|
|
|
-#else /* !HAS_LIBSSH2_SFTP_FSYNC */
|
|
+#else /* !HAVE_LIBSSH_0_8 */
|
|
|
|
static coroutine_fn int ssh_co_flush(BlockDriverState *bs)
|
|
{
|
|
BDRVSSHState *s = bs->opaque;
|
|
|
|
- unsafe_flush_warning(s, "libssh2 >= 1.4.4");
|
|
+ unsafe_flush_warning(s, "libssh >= 0.8.0");
|
|
return 0;
|
|
}
|
|
|
|
-#endif /* !HAS_LIBSSH2_SFTP_FSYNC */
|
|
+#endif /* !HAVE_LIBSSH_0_8 */
|
|
|
|
static int64_t ssh_getlength(BlockDriverState *bs)
|
|
{
|
|
BDRVSSHState *s = bs->opaque;
|
|
int64_t length;
|
|
|
|
- /* Note we cannot make a libssh2 call here. */
|
|
- length = (int64_t) s->attrs.filesize;
|
|
+ /* Note we cannot make a libssh call here. */
|
|
+ length = (int64_t) s->attrs->size;
|
|
trace_ssh_getlength(length);
|
|
|
|
return length;
|
|
@@ -1237,12 +1301,12 @@ static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
- if (offset < s->attrs.filesize) {
|
|
+ if (offset < s->attrs->size) {
|
|
error_setg(errp, "ssh driver does not support shrinking files");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
- if (offset == s->attrs.filesize) {
|
|
+ if (offset == s->attrs->size) {
|
|
return 0;
|
|
}
|
|
|
|
@@ -1307,12 +1371,16 @@ static void bdrv_ssh_init(void)
|
|
{
|
|
int r;
|
|
|
|
- r = libssh2_init(0);
|
|
+ r = ssh_init();
|
|
if (r != 0) {
|
|
- fprintf(stderr, "libssh2 initialization failed, %d\n", r);
|
|
+ fprintf(stderr, "libssh initialization failed, %d\n", r);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
+#if TRACE_LIBSSH != 0
|
|
+ ssh_set_log_level(TRACE_LIBSSH);
|
|
+#endif
|
|
+
|
|
bdrv_register(&bdrv_ssh);
|
|
}
|
|
|
|
diff --git a/block/trace-events b/block/trace-events
|
|
index 23c9963..0017e33 100644
|
|
--- a/block/trace-events
|
|
+++ b/block/trace-events
|
|
@@ -154,19 +154,21 @@ nvme_cmd_map_qiov_iov(void *s, int i, void *page, int pages) "s %p iov[%d] %p pa
|
|
# block/ssh.c
|
|
ssh_restart_coroutine(void *co) "co=%p"
|
|
ssh_flush(void) "fsync"
|
|
-ssh_check_host_key_knownhosts(const char *key) "host key OK: %s"
|
|
+ssh_check_host_key_knownhosts(void) "host key OK"
|
|
ssh_connect_to_ssh(char *path, int flags, int mode) "opening file %s flags=0x%x creat_mode=0%o"
|
|
ssh_co_yield(int sock, void *rd_handler, void *wr_handler) "s->sock=%d rd_handler=%p wr_handler=%p"
|
|
ssh_co_yield_back(int sock) "s->sock=%d - back"
|
|
ssh_getlength(int64_t length) "length=%" PRIi64
|
|
ssh_co_create_opts(uint64_t size) "total_size=%" PRIu64
|
|
ssh_read(int64_t offset, size_t size) "offset=%" PRIi64 " size=%zu"
|
|
-ssh_read_buf(void *buf, size_t size) "sftp_read buf=%p size=%zu"
|
|
-ssh_read_return(ssize_t ret) "sftp_read returned %zd"
|
|
+ssh_read_buf(void *buf, size_t size, size_t actual_size) "sftp_read buf=%p size=%zu (actual size=%zu)"
|
|
+ssh_read_return(ssize_t ret, int sftp_err) "sftp_read returned %zd (sftp error=%d)"
|
|
ssh_write(int64_t offset, size_t size) "offset=%" PRIi64 " size=%zu"
|
|
-ssh_write_buf(void *buf, size_t size) "sftp_write buf=%p size=%zu"
|
|
-ssh_write_return(ssize_t ret) "sftp_write returned %zd"
|
|
+ssh_write_buf(void *buf, size_t size, size_t actual_size) "sftp_write buf=%p size=%zu (actual size=%zu)"
|
|
+ssh_write_return(ssize_t ret, int sftp_err) "sftp_write returned %zd (sftp error=%d)"
|
|
ssh_seek(int64_t offset) "seeking to offset=%" PRIi64
|
|
+ssh_auth_methods(int methods) "auth methods=0x%x"
|
|
+ssh_server_status(int status) "server status=%d"
|
|
|
|
# ssh.c
|
|
-sftp_error(const char *op, const char *ssh_err, int ssh_err_code, unsigned long sftp_err_code) "%s failed: %s (libssh2 error code: %d, sftp error code: %lu)"
|
|
+sftp_error(const char *op, const char *ssh_err, int ssh_err_code, int sftp_err_code) "%s failed: %s (libssh error code: %d, sftp error code: %d)"
|
|
diff --git a/configure b/configure
|
|
index c9a1034..eefec38 100755
|
|
--- a/configure
|
|
+++ b/configure
|
|
@@ -445,7 +445,7 @@ gcrypt_kdf="no"
|
|
vte=""
|
|
virglrenderer=""
|
|
tpm="yes"
|
|
-libssh2=""
|
|
+libssh=""
|
|
live_block_migration="yes"
|
|
numa=""
|
|
tcmalloc="no"
|
|
@@ -1334,9 +1334,9 @@ for opt do
|
|
;;
|
|
--enable-tpm) tpm="yes"
|
|
;;
|
|
- --disable-libssh2) libssh2="no"
|
|
+ --disable-libssh) libssh="no"
|
|
;;
|
|
- --enable-libssh2) libssh2="yes"
|
|
+ --enable-libssh) libssh="yes"
|
|
;;
|
|
--disable-live-block-migration) live_block_migration="no"
|
|
;;
|
|
@@ -1669,7 +1669,7 @@ disabled with --disable-FEATURE, default is enabled if available:
|
|
coroutine-pool coroutine freelist (better performance)
|
|
glusterfs GlusterFS backend
|
|
tpm TPM support
|
|
- libssh2 ssh block device support
|
|
+ libssh ssh block device support
|
|
numa libnuma support
|
|
libxml2 for Parallels image format
|
|
tcmalloc tcmalloc support
|
|
@@ -3656,43 +3656,34 @@ EOF
|
|
fi
|
|
|
|
##########################################
|
|
-# libssh2 probe
|
|
-min_libssh2_version=1.2.8
|
|
-if test "$libssh2" != "no" ; then
|
|
- if $pkg_config --atleast-version=$min_libssh2_version libssh2; then
|
|
- libssh2_cflags=$($pkg_config libssh2 --cflags)
|
|
- libssh2_libs=$($pkg_config libssh2 --libs)
|
|
- libssh2=yes
|
|
+# libssh probe
|
|
+if test "$libssh" != "no" ; then
|
|
+ if $pkg_config --exists libssh; then
|
|
+ libssh_cflags=$($pkg_config libssh --cflags)
|
|
+ libssh_libs=$($pkg_config libssh --libs)
|
|
+ libssh=yes
|
|
else
|
|
- if test "$libssh2" = "yes" ; then
|
|
- error_exit "libssh2 >= $min_libssh2_version required for --enable-libssh2"
|
|
+ if test "$libssh" = "yes" ; then
|
|
+ error_exit "libssh required for --enable-libssh"
|
|
fi
|
|
- libssh2=no
|
|
+ libssh=no
|
|
fi
|
|
fi
|
|
|
|
##########################################
|
|
-# libssh2_sftp_fsync probe
|
|
+# Check for libssh 0.8
|
|
+# This is done like this instead of using the LIBSSH_VERSION_* and
|
|
+# SSH_VERSION_* macros because some distributions in the past shipped
|
|
+# snapshots of the future 0.8 from Git, and those snapshots did not
|
|
+# have updated version numbers (still referring to 0.7.0).
|
|
|
|
-if test "$libssh2" = "yes"; then
|
|
+if test "$libssh" = "yes"; then
|
|
cat > $TMPC <<EOF
|
|
-#include <stdio.h>
|
|
-#include <libssh2.h>
|
|
-#include <libssh2_sftp.h>
|
|
-int main(void) {
|
|
- LIBSSH2_SESSION *session;
|
|
- LIBSSH2_SFTP *sftp;
|
|
- LIBSSH2_SFTP_HANDLE *sftp_handle;
|
|
- session = libssh2_session_init ();
|
|
- sftp = libssh2_sftp_init (session);
|
|
- sftp_handle = libssh2_sftp_open (sftp, "/", 0, 0);
|
|
- libssh2_sftp_fsync (sftp_handle);
|
|
- return 0;
|
|
-}
|
|
+#include <libssh/libssh.h>
|
|
+int main(void) { return ssh_get_server_publickey(NULL, NULL); }
|
|
EOF
|
|
- # libssh2_cflags/libssh2_libs defined in previous test.
|
|
- if compile_prog "$libssh2_cflags" "$libssh2_libs" ; then
|
|
- QEMU_CFLAGS="-DHAS_LIBSSH2_SFTP_FSYNC $QEMU_CFLAGS"
|
|
+ if compile_prog "$libssh_cflags" "$libssh_libs"; then
|
|
+ libssh_cflags="-DHAVE_LIBSSH_0_8 $libssh_cflags"
|
|
fi
|
|
fi
|
|
|
|
@@ -5998,7 +5989,7 @@ echo "GlusterFS support $glusterfs"
|
|
echo "gcov $gcov_tool"
|
|
echo "gcov enabled $gcov"
|
|
echo "TPM support $tpm"
|
|
-echo "libssh2 support $libssh2"
|
|
+echo "libssh support $libssh"
|
|
echo "TPM passthrough $tpm_passthrough"
|
|
echo "TPM emulator $tpm_emulator"
|
|
echo "QOM debugging $qom_cast_debug"
|
|
@@ -6664,10 +6655,10 @@ if test "$glusterfs_iocb_has_stat" = "yes" ; then
|
|
echo "CONFIG_GLUSTERFS_IOCB_HAS_STAT=y" >> $config_host_mak
|
|
fi
|
|
|
|
-if test "$libssh2" = "yes" ; then
|
|
- echo "CONFIG_LIBSSH2=m" >> $config_host_mak
|
|
- echo "LIBSSH2_CFLAGS=$libssh2_cflags" >> $config_host_mak
|
|
- echo "LIBSSH2_LIBS=$libssh2_libs" >> $config_host_mak
|
|
+if test "$libssh" = "yes" ; then
|
|
+ echo "CONFIG_LIBSSH=m" >> $config_host_mak
|
|
+ echo "LIBSSH_CFLAGS=$libssh_cflags" >> $config_host_mak
|
|
+ echo "LIBSSH_LIBS=$libssh_libs" >> $config_host_mak
|
|
fi
|
|
|
|
if test "$live_block_migration" = "yes" ; then
|
|
diff --git a/docs/qemu-block-drivers.texi b/docs/qemu-block-drivers.texi
|
|
index e0d752a..5004dec 100644
|
|
--- a/docs/qemu-block-drivers.texi
|
|
+++ b/docs/qemu-block-drivers.texi
|
|
@@ -782,7 +782,7 @@ print a warning when @code{fsync} is not supported:
|
|
|
|
warning: ssh server @code{ssh.example.com:22} does not support fsync
|
|
|
|
-With sufficiently new versions of libssh2 and OpenSSH, @code{fsync} is
|
|
+With sufficiently new versions of libssh and OpenSSH, @code{fsync} is
|
|
supported.
|
|
|
|
@node disk_images_nvme
|
|
diff --git a/tests/docker/dockerfiles/debian-win32-cross.docker b/tests/docker/dockerfiles/debian-win32-cross.docker
|
|
index dd021f2..0a4970c 100644
|
|
--- a/tests/docker/dockerfiles/debian-win32-cross.docker
|
|
+++ b/tests/docker/dockerfiles/debian-win32-cross.docker
|
|
@@ -15,7 +15,6 @@ RUN DEBIAN_FRONTEND=noninteractive eatmydata \
|
|
mxe-$TARGET-w64-mingw32.shared-curl \
|
|
mxe-$TARGET-w64-mingw32.shared-glib \
|
|
mxe-$TARGET-w64-mingw32.shared-libgcrypt \
|
|
- mxe-$TARGET-w64-mingw32.shared-libssh2 \
|
|
mxe-$TARGET-w64-mingw32.shared-libusb1 \
|
|
mxe-$TARGET-w64-mingw32.shared-lzo \
|
|
mxe-$TARGET-w64-mingw32.shared-nettle \
|
|
diff --git a/tests/docker/dockerfiles/debian-win64-cross.docker b/tests/docker/dockerfiles/debian-win64-cross.docker
|
|
index 4542bcc..b27985b 100644
|
|
--- a/tests/docker/dockerfiles/debian-win64-cross.docker
|
|
+++ b/tests/docker/dockerfiles/debian-win64-cross.docker
|
|
@@ -15,7 +15,6 @@ RUN DEBIAN_FRONTEND=noninteractive eatmydata \
|
|
mxe-$TARGET-w64-mingw32.shared-curl \
|
|
mxe-$TARGET-w64-mingw32.shared-glib \
|
|
mxe-$TARGET-w64-mingw32.shared-libgcrypt \
|
|
- mxe-$TARGET-w64-mingw32.shared-libssh2 \
|
|
mxe-$TARGET-w64-mingw32.shared-libusb1 \
|
|
mxe-$TARGET-w64-mingw32.shared-lzo \
|
|
mxe-$TARGET-w64-mingw32.shared-nettle \
|
|
diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker
|
|
index b706f42..2dd598d 100644
|
|
--- a/tests/docker/dockerfiles/fedora.docker
|
|
+++ b/tests/docker/dockerfiles/fedora.docker
|
|
@@ -6,18 +6,18 @@ ENV PACKAGES \
|
|
bluez-libs-devel brlapi-devel bzip2-devel \
|
|
device-mapper-multipath-devel glusterfs-api-devel gnutls-devel \
|
|
gtk3-devel libattr-devel libcap-devel libcap-ng-devel libcurl-devel \
|
|
- libjpeg-devel libpng-devel librbd-devel libssh2-devel libusbx-devel \
|
|
+ libjpeg-devel libpng-devel librbd-devel libssh-devel libusbx-devel \
|
|
libxml2-devel lzo-devel ncurses-devel nettle-devel nss-devel \
|
|
numactl-devel SDL2-devel snappy-devel spice-server-devel \
|
|
systemtap-sdt-devel usbredir-devel virglrenderer-devel vte3-devel \
|
|
xen-devel \
|
|
mingw32-pixman mingw32-glib2 mingw32-gmp mingw32-SDL mingw32-pkg-config \
|
|
mingw32-gtk2 mingw32-gtk3 mingw32-gnutls mingw32-nettle mingw32-libtasn1 \
|
|
- mingw32-libjpeg-turbo mingw32-libpng mingw32-curl mingw32-libssh2 \
|
|
+ mingw32-libjpeg-turbo mingw32-libpng mingw32-curl \
|
|
mingw32-bzip2 \
|
|
mingw64-pixman mingw64-glib2 mingw64-gmp mingw64-SDL mingw64-pkg-config \
|
|
mingw64-gtk2 mingw64-gtk3 mingw64-gnutls mingw64-nettle mingw64-libtasn1 \
|
|
- mingw64-libjpeg-turbo mingw64-libpng mingw64-curl mingw64-libssh2 \
|
|
+ mingw64-libjpeg-turbo mingw64-libpng mingw64-curl \
|
|
mingw64-bzip2
|
|
ENV QEMU_CONFIGURE_OPTS --python=/usr/bin/python3
|
|
|
|
diff --git a/tests/docker/dockerfiles/ubuntu.docker b/tests/docker/dockerfiles/ubuntu.docker
|
|
index dabbf2a..be37ff1 100644
|
|
--- a/tests/docker/dockerfiles/ubuntu.docker
|
|
+++ b/tests/docker/dockerfiles/ubuntu.docker
|
|
@@ -4,7 +4,7 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu/ trusty universe multiverse" >> \
|
|
RUN apt-get update
|
|
ENV PACKAGES flex bison \
|
|
libusb-1.0-0-dev libiscsi-dev librados-dev libncurses5-dev libncursesw5-dev \
|
|
- libseccomp-dev libgnutls-dev libssh2-1-dev libspice-server-dev \
|
|
+ libseccomp-dev libgnutls-dev libssh-dev libspice-server-dev \
|
|
libspice-protocol-dev libnss3-dev libfdt-dev \
|
|
libgtk-3-dev libvte-2.91-dev libsdl1.2-dev libpng12-dev libpixman-1-dev \
|
|
libvdeplug-dev liblzo2-dev libsnappy-dev libbz2-dev libxen-dev librdmacm-dev libibverbs-dev \
|
|
diff --git a/tests/qemu-iotests/207 b/tests/qemu-iotests/207
|
|
index aaad656..219f1a6 100755
|
|
--- a/tests/qemu-iotests/207
|
|
+++ b/tests/qemu-iotests/207
|
|
@@ -106,12 +106,49 @@ with iotests.FilePath('t.img') as disk_path, \
|
|
|
|
iotests.img_info_log(remote_path)
|
|
|
|
- md5_key = subprocess.check_output(
|
|
- 'ssh-keyscan -t rsa 127.0.0.1 2>/dev/null | grep -v "\\^#" | ' +
|
|
- 'cut -d" " -f3 | base64 -d | md5sum -b | cut -d" " -f1',
|
|
- shell=True).rstrip().decode('ascii')
|
|
+ keys = subprocess.check_output(
|
|
+ 'ssh-keyscan 127.0.0.1 2>/dev/null | grep -v "\\^#" | ' +
|
|
+ 'cut -d" " -f3',
|
|
+ shell=True).rstrip().decode('ascii').split('\n')
|
|
+
|
|
+ # Mappings of base64 representations to digests
|
|
+ md5_keys = {}
|
|
+ sha1_keys = {}
|
|
+
|
|
+ for key in keys:
|
|
+ md5_keys[key] = subprocess.check_output(
|
|
+ 'echo %s | base64 -d | md5sum -b | cut -d" " -f1' % key,
|
|
+ shell=True).rstrip().decode('ascii')
|
|
+
|
|
+ sha1_keys[key] = subprocess.check_output(
|
|
+ 'echo %s | base64 -d | sha1sum -b | cut -d" " -f1' % key,
|
|
+ shell=True).rstrip().decode('ascii')
|
|
|
|
vm.launch()
|
|
+
|
|
+ # Find correct key first
|
|
+ matching_key = None
|
|
+ for key in keys:
|
|
+ result = vm.qmp('blockdev-add',
|
|
+ driver='ssh', node_name='node0', path=disk_path,
|
|
+ server={
|
|
+ 'host': '127.0.0.1',
|
|
+ 'port': '22',
|
|
+ }, host_key_check={
|
|
+ 'mode': 'hash',
|
|
+ 'type': 'md5',
|
|
+ 'hash': md5_keys[key],
|
|
+ })
|
|
+
|
|
+ if 'error' not in result:
|
|
+ vm.qmp('blockdev-del', node_name='node0')
|
|
+ matching_key = key
|
|
+ break
|
|
+
|
|
+ if matching_key is None:
|
|
+ vm.shutdown()
|
|
+ iotests.notrun('Did not find a key that fits 127.0.0.1')
|
|
+
|
|
blockdev_create(vm, { 'driver': 'ssh',
|
|
'location': {
|
|
'path': disk_path,
|
|
@@ -136,7 +173,7 @@ with iotests.FilePath('t.img') as disk_path, \
|
|
'host-key-check': {
|
|
'mode': 'hash',
|
|
'type': 'md5',
|
|
- 'hash': md5_key,
|
|
+ 'hash': md5_keys[matching_key],
|
|
}
|
|
},
|
|
'size': 8388608 })
|
|
@@ -144,11 +181,6 @@ with iotests.FilePath('t.img') as disk_path, \
|
|
|
|
iotests.img_info_log(remote_path)
|
|
|
|
- sha1_key = subprocess.check_output(
|
|
- 'ssh-keyscan -t rsa 127.0.0.1 2>/dev/null | grep -v "\\^#" | ' +
|
|
- 'cut -d" " -f3 | base64 -d | sha1sum -b | cut -d" " -f1',
|
|
- shell=True).rstrip().decode('ascii')
|
|
-
|
|
vm.launch()
|
|
blockdev_create(vm, { 'driver': 'ssh',
|
|
'location': {
|
|
@@ -174,7 +206,7 @@ with iotests.FilePath('t.img') as disk_path, \
|
|
'host-key-check': {
|
|
'mode': 'hash',
|
|
'type': 'sha1',
|
|
- 'hash': sha1_key,
|
|
+ 'hash': sha1_keys[matching_key],
|
|
}
|
|
},
|
|
'size': 4194304 })
|
|
diff --git a/tests/qemu-iotests/207.out b/tests/qemu-iotests/207.out
|
|
index 789b465..48318ce 100644
|
|
--- a/tests/qemu-iotests/207.out
|
|
+++ b/tests/qemu-iotests/207.out
|
|
@@ -68,7 +68,7 @@ virtual size: 4.0M (4194304 bytes)
|
|
|
|
{"execute": "blockdev-create", "arguments": {"job_id": "job0", "options": {"driver": "ssh", "location": {"host-key-check": {"mode": "none"}, "path": "/this/is/not/an/existing/path", "server": {"host": "127.0.0.1", "port": "22"}}, "size": 4194304}}}
|
|
{"return": {}}
|
|
-Job failed: failed to open remote file '/this/is/not/an/existing/path': Failed opening remote file (libssh2 error code: -31)
|
|
+Job failed: failed to open remote file '/this/is/not/an/existing/path': SFTP server: No such file (libssh error code: 1, sftp error code: 2)
|
|
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
|
|
{"return": {}}
|
|
|
|
--
|
|
1.8.3.1
|
|
|