diff options
author | Pino Toscano <ptoscano@redhat.com> | 2019-06-20 22:08:40 +0200 |
---|---|---|
committer | Max Reitz <mreitz@redhat.com> | 2019-06-24 16:01:04 +0200 |
commit | b10d49d7619e4957b4b971f816661b57e5061d71 (patch) | |
tree | bf8bf1c96bf8ffe72bcc4928b27e70654d25b6eb /block/ssh.c | |
parent | 98eb9733f4cf2eeab6d12db7e758665d2fd5367b (diff) |
ssh: switch from libssh2 to libssh
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>
Diffstat (limited to 'block/ssh.c')
-rw-r--r-- | block/ssh.c | 652 |
1 files changed, 360 insertions, 292 deletions
diff --git a/block/ssh.c b/block/ssh.c index 6da7b9cbfe..501933b855 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" @@ -46,13 +46,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. */ @@ -60,18 +58,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; @@ -91,7 +86,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); } @@ -99,20 +93,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 */ } } @@ -127,13 +119,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); @@ -152,18 +144,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); @@ -173,15 +165,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); } @@ -282,82 +274,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 SSH_SERVER_FILE_NOT_FOUND: + ret = -ENOENT; + error_setg(errp, "known_hosts file not found"); goto out; - case LIBSSH2_KNOWNHOST_CHECK_FAILURE: + 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; } @@ -401,18 +431,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; @@ -421,8 +467,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; @@ -438,15 +483,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(); } @@ -454,60 +499,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; @@ -515,13 +543,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; } @@ -640,7 +661,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); @@ -657,71 +679,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; } @@ -729,21 +827,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; } @@ -758,9 +862,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); @@ -775,18 +881,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; @@ -797,25 +898,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; } @@ -843,8 +944,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; @@ -913,10 +1013,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; @@ -953,12 +1051,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; } @@ -970,33 +1068,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) @@ -1008,7 +1079,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 @@ -1018,35 +1090,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; @@ -1083,7 +1155,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 @@ -1094,46 +1167,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; @@ -1168,24 +1232,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; @@ -1206,25 +1270,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; @@ -1241,12 +1305,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; } @@ -1341,12 +1405,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); } |