xref: /qemu/block/ssh.c (revision 60f782b6)
10a12ec87SRichard W.M. Jones /*
20a12ec87SRichard W.M. Jones  * Secure Shell (ssh) backend for QEMU.
30a12ec87SRichard W.M. Jones  *
40a12ec87SRichard W.M. Jones  * Copyright (C) 2013 Red Hat Inc., Richard W.M. Jones <rjones@redhat.com>
50a12ec87SRichard W.M. Jones  *
60a12ec87SRichard W.M. Jones  * Permission is hereby granted, free of charge, to any person obtaining a copy
70a12ec87SRichard W.M. Jones  * of this software and associated documentation files (the "Software"), to deal
80a12ec87SRichard W.M. Jones  * in the Software without restriction, including without limitation the rights
90a12ec87SRichard W.M. Jones  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
100a12ec87SRichard W.M. Jones  * copies of the Software, and to permit persons to whom the Software is
110a12ec87SRichard W.M. Jones  * furnished to do so, subject to the following conditions:
120a12ec87SRichard W.M. Jones  *
130a12ec87SRichard W.M. Jones  * The above copyright notice and this permission notice shall be included in
140a12ec87SRichard W.M. Jones  * all copies or substantial portions of the Software.
150a12ec87SRichard W.M. Jones  *
160a12ec87SRichard W.M. Jones  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
170a12ec87SRichard W.M. Jones  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
180a12ec87SRichard W.M. Jones  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
190a12ec87SRichard W.M. Jones  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
200a12ec87SRichard W.M. Jones  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
210a12ec87SRichard W.M. Jones  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
220a12ec87SRichard W.M. Jones  * THE SOFTWARE.
230a12ec87SRichard W.M. Jones  */
240a12ec87SRichard W.M. Jones 
2580c71a24SPeter Maydell #include "qemu/osdep.h"
260a12ec87SRichard W.M. Jones 
27b10d49d7SPino Toscano #include <libssh/libssh.h>
28b10d49d7SPino Toscano #include <libssh/sftp.h>
290a12ec87SRichard W.M. Jones 
30e2c1c34fSMarkus Armbruster #include "block/block-io.h"
310a12ec87SRichard W.M. Jones #include "block/block_int.h"
32609f45eaSMax Reitz #include "block/qdict.h"
33da34e65cSMarkus Armbruster #include "qapi/error.h"
34d49b6836SMarkus Armbruster #include "qemu/error-report.h"
350b8fa32fSMarkus Armbruster #include "qemu/module.h"
36922a01a0SMarkus Armbruster #include "qemu/option.h"
37856dfd8aSMarkus Armbruster #include "qemu/ctype.h"
380da5b8efSAshijeet Acharya #include "qemu/cutils.h"
390a12ec87SRichard W.M. Jones #include "qemu/sockets.h"
400a12ec87SRichard W.M. Jones #include "qemu/uri.h"
419af23989SMarkus Armbruster #include "qapi/qapi-visit-sockets.h"
4216e4bdb1SKevin Wolf #include "qapi/qapi-visit-block-core.h"
43452fcdbcSMarkus Armbruster #include "qapi/qmp/qdict.h"
44d49b6836SMarkus Armbruster #include "qapi/qmp/qstring.h"
450da5b8efSAshijeet Acharya #include "qapi/qobject-input-visitor.h"
460da5b8efSAshijeet Acharya #include "qapi/qobject-output-visitor.h"
47023908a2SLaurent Vivier #include "trace.h"
480a12ec87SRichard W.M. Jones 
49023908a2SLaurent Vivier /*
50b10d49d7SPino Toscano  * TRACE_LIBSSH=<level> enables tracing in libssh itself.
51b10d49d7SPino Toscano  * The meaning of <level> is described here:
52b10d49d7SPino Toscano  * http://api.libssh.org/master/group__libssh__log.html
530a12ec87SRichard W.M. Jones  */
54b10d49d7SPino Toscano #define TRACE_LIBSSH  0 /* see: SSH_LOG_* */
550a12ec87SRichard W.M. Jones 
560a12ec87SRichard W.M. Jones typedef struct BDRVSSHState {
570a12ec87SRichard W.M. Jones     /* Coroutine. */
580a12ec87SRichard W.M. Jones     CoMutex lock;
590a12ec87SRichard W.M. Jones 
600a12ec87SRichard W.M. Jones     /* SSH connection. */
610a12ec87SRichard W.M. Jones     int sock;                         /* socket */
62b10d49d7SPino Toscano     ssh_session session;              /* ssh session */
63b10d49d7SPino Toscano     sftp_session sftp;                /* sftp session */
64b10d49d7SPino Toscano     sftp_file sftp_handle;            /* sftp remote file handle */
650a12ec87SRichard W.M. Jones 
66b10d49d7SPino Toscano     /*
67b10d49d7SPino Toscano      * File attributes at open.  We try to keep the .size field
680a12ec87SRichard W.M. Jones      * updated if it changes (eg by writing at the end of the file).
690a12ec87SRichard W.M. Jones      */
70b10d49d7SPino Toscano     sftp_attributes attrs;
719a2d462eSRichard W.M. Jones 
720da5b8efSAshijeet Acharya     InetSocketAddress *inet;
730da5b8efSAshijeet Acharya 
749a2d462eSRichard W.M. Jones     /* Used to warn if 'flush' is not supported. */
759a2d462eSRichard W.M. Jones     bool unsafe_flush_warning;
76b8c1f901SMax Reitz 
77b8c1f901SMax Reitz     /*
78b8c1f901SMax Reitz      * Store the user name for ssh_refresh_filename() because the
79b8c1f901SMax Reitz      * default depends on the system you are on -- therefore, when we
80b8c1f901SMax Reitz      * generate a filename, it should always contain the user name we
81b8c1f901SMax Reitz      * are actually using.
82b8c1f901SMax Reitz      */
83b8c1f901SMax Reitz     char *user;
840a12ec87SRichard W.M. Jones } BDRVSSHState;
850a12ec87SRichard W.M. Jones 
ssh_state_init(BDRVSSHState * s)860a12ec87SRichard W.M. Jones static void ssh_state_init(BDRVSSHState *s)
870a12ec87SRichard W.M. Jones {
880a12ec87SRichard W.M. Jones     memset(s, 0, sizeof *s);
890a12ec87SRichard W.M. Jones     s->sock = -1;
900a12ec87SRichard W.M. Jones     qemu_co_mutex_init(&s->lock);
910a12ec87SRichard W.M. Jones }
920a12ec87SRichard W.M. Jones 
ssh_state_free(BDRVSSHState * s)930a12ec87SRichard W.M. Jones static void ssh_state_free(BDRVSSHState *s)
940a12ec87SRichard W.M. Jones {
95b8c1f901SMax Reitz     g_free(s->user);
96b8c1f901SMax Reitz 
97b10d49d7SPino Toscano     if (s->attrs) {
98b10d49d7SPino Toscano         sftp_attributes_free(s->attrs);
99b10d49d7SPino Toscano     }
1000a12ec87SRichard W.M. Jones     if (s->sftp_handle) {
101b10d49d7SPino Toscano         sftp_close(s->sftp_handle);
1020a12ec87SRichard W.M. Jones     }
1030a12ec87SRichard W.M. Jones     if (s->sftp) {
104b10d49d7SPino Toscano         sftp_free(s->sftp);
1050a12ec87SRichard W.M. Jones     }
1060a12ec87SRichard W.M. Jones     if (s->session) {
107b10d49d7SPino Toscano         ssh_disconnect(s->session);
108b10d49d7SPino Toscano         ssh_free(s->session); /* This frees s->sock */
1090a12ec87SRichard W.M. Jones     }
1100a12ec87SRichard W.M. Jones }
1110a12ec87SRichard W.M. Jones 
1129edc6313SMarc-André Lureau static void G_GNUC_PRINTF(3, 4)
session_error_setg(Error ** errp,BDRVSSHState * s,const char * fs,...)11301c2b265SMarkus Armbruster session_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
11401c2b265SMarkus Armbruster {
11501c2b265SMarkus Armbruster     va_list args;
11601c2b265SMarkus Armbruster     char *msg;
11701c2b265SMarkus Armbruster 
11801c2b265SMarkus Armbruster     va_start(args, fs);
11901c2b265SMarkus Armbruster     msg = g_strdup_vprintf(fs, args);
12001c2b265SMarkus Armbruster     va_end(args);
12101c2b265SMarkus Armbruster 
12201c2b265SMarkus Armbruster     if (s->session) {
123b10d49d7SPino Toscano         const char *ssh_err;
12401c2b265SMarkus Armbruster         int ssh_err_code;
12501c2b265SMarkus Armbruster 
126b10d49d7SPino Toscano         /* This is not an errno.  See <libssh/libssh.h>. */
127b10d49d7SPino Toscano         ssh_err = ssh_get_error(s->session);
128b10d49d7SPino Toscano         ssh_err_code = ssh_get_error_code(s->session);
129b10d49d7SPino Toscano         error_setg(errp, "%s: %s (libssh error code: %d)",
13001c2b265SMarkus Armbruster                    msg, ssh_err, ssh_err_code);
13101c2b265SMarkus Armbruster     } else {
13201c2b265SMarkus Armbruster         error_setg(errp, "%s", msg);
13301c2b265SMarkus Armbruster     }
13401c2b265SMarkus Armbruster     g_free(msg);
13501c2b265SMarkus Armbruster }
13601c2b265SMarkus Armbruster 
1379edc6313SMarc-André Lureau static void G_GNUC_PRINTF(3, 4)
sftp_error_setg(Error ** errp,BDRVSSHState * s,const char * fs,...)1385496fb1aSMarkus Armbruster sftp_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
1390a12ec87SRichard W.M. Jones {
1400a12ec87SRichard W.M. Jones     va_list args;
1415496fb1aSMarkus Armbruster     char *msg;
1420a12ec87SRichard W.M. Jones 
1430a12ec87SRichard W.M. Jones     va_start(args, fs);
1445496fb1aSMarkus Armbruster     msg = g_strdup_vprintf(fs, args);
1455496fb1aSMarkus Armbruster     va_end(args);
1460a12ec87SRichard W.M. Jones 
1475496fb1aSMarkus Armbruster     if (s->sftp) {
148b10d49d7SPino Toscano         const char *ssh_err;
1490a12ec87SRichard W.M. Jones         int ssh_err_code;
150b10d49d7SPino Toscano         int sftp_err_code;
1510a12ec87SRichard W.M. Jones 
152b10d49d7SPino Toscano         /* This is not an errno.  See <libssh/libssh.h>. */
153b10d49d7SPino Toscano         ssh_err = ssh_get_error(s->session);
154b10d49d7SPino Toscano         ssh_err_code = ssh_get_error_code(s->session);
155b10d49d7SPino Toscano         /* See <libssh/sftp.h>. */
156b10d49d7SPino Toscano         sftp_err_code = sftp_get_error(s->sftp);
1570a12ec87SRichard W.M. Jones 
1585496fb1aSMarkus Armbruster         error_setg(errp,
159b10d49d7SPino Toscano                    "%s: %s (libssh error code: %d, sftp error code: %d)",
1605496fb1aSMarkus Armbruster                    msg, ssh_err, ssh_err_code, sftp_err_code);
1615496fb1aSMarkus Armbruster     } else {
1625496fb1aSMarkus Armbruster         error_setg(errp, "%s", msg);
1635496fb1aSMarkus Armbruster     }
1645496fb1aSMarkus Armbruster     g_free(msg);
1650a12ec87SRichard W.M. Jones }
1660a12ec87SRichard W.M. Jones 
sftp_error_trace(BDRVSSHState * s,const char * op)1676b3048ceSMarkus Armbruster static void sftp_error_trace(BDRVSSHState *s, const char *op)
1680a12ec87SRichard W.M. Jones {
169b10d49d7SPino Toscano     const char *ssh_err;
1700a12ec87SRichard W.M. Jones     int ssh_err_code;
171b10d49d7SPino Toscano     int sftp_err_code;
1720a12ec87SRichard W.M. Jones 
173b10d49d7SPino Toscano     /* This is not an errno.  See <libssh/libssh.h>. */
174b10d49d7SPino Toscano     ssh_err = ssh_get_error(s->session);
175b10d49d7SPino Toscano     ssh_err_code = ssh_get_error_code(s->session);
176b10d49d7SPino Toscano     /* See <libssh/sftp.h>. */
177b10d49d7SPino Toscano     sftp_err_code = sftp_get_error(s->sftp);
1780a12ec87SRichard W.M. Jones 
1796b3048ceSMarkus Armbruster     trace_sftp_error(op, ssh_err, ssh_err_code, sftp_err_code);
1800a12ec87SRichard W.M. Jones }
1810a12ec87SRichard W.M. Jones 
parse_uri(const char * filename,QDict * options,Error ** errp)1820a12ec87SRichard W.M. Jones static int parse_uri(const char *filename, QDict *options, Error **errp)
1830a12ec87SRichard W.M. Jones {
1840a12ec87SRichard W.M. Jones     URI *uri = NULL;
185eab2ac9dSPaolo Bonzini     QueryParams *qp;
1861059f1bbSAshijeet Acharya     char *port_str;
1870a12ec87SRichard W.M. Jones     int i;
1880a12ec87SRichard W.M. Jones 
1890a12ec87SRichard W.M. Jones     uri = uri_parse(filename);
1900a12ec87SRichard W.M. Jones     if (!uri) {
1910a12ec87SRichard W.M. Jones         return -EINVAL;
1920a12ec87SRichard W.M. Jones     }
1930a12ec87SRichard W.M. Jones 
194f69165a8SMax Reitz     if (g_strcmp0(uri->scheme, "ssh") != 0) {
1950a12ec87SRichard W.M. Jones         error_setg(errp, "URI scheme must be 'ssh'");
1960a12ec87SRichard W.M. Jones         goto err;
1970a12ec87SRichard W.M. Jones     }
1980a12ec87SRichard W.M. Jones 
1990a12ec87SRichard W.M. Jones     if (!uri->server || strcmp(uri->server, "") == 0) {
2000a12ec87SRichard W.M. Jones         error_setg(errp, "missing hostname in URI");
2010a12ec87SRichard W.M. Jones         goto err;
2020a12ec87SRichard W.M. Jones     }
2030a12ec87SRichard W.M. Jones 
2040a12ec87SRichard W.M. Jones     if (!uri->path || strcmp(uri->path, "") == 0) {
2050a12ec87SRichard W.M. Jones         error_setg(errp, "missing remote path in URI");
2060a12ec87SRichard W.M. Jones         goto err;
2070a12ec87SRichard W.M. Jones     }
2080a12ec87SRichard W.M. Jones 
2090a12ec87SRichard W.M. Jones     qp = query_params_parse(uri->query);
2100a12ec87SRichard W.M. Jones     if (!qp) {
2110a12ec87SRichard W.M. Jones         error_setg(errp, "could not parse query parameters");
2120a12ec87SRichard W.M. Jones         goto err;
2130a12ec87SRichard W.M. Jones     }
2140a12ec87SRichard W.M. Jones 
2150a12ec87SRichard W.M. Jones     if(uri->user && strcmp(uri->user, "") != 0) {
21646f5ac20SEric Blake         qdict_put_str(options, "user", uri->user);
2170a12ec87SRichard W.M. Jones     }
2180a12ec87SRichard W.M. Jones 
21946f5ac20SEric Blake     qdict_put_str(options, "server.host", uri->server);
2200a12ec87SRichard W.M. Jones 
2211059f1bbSAshijeet Acharya     port_str = g_strdup_printf("%d", uri->port ?: 22);
22246f5ac20SEric Blake     qdict_put_str(options, "server.port", port_str);
2231059f1bbSAshijeet Acharya     g_free(port_str);
2240a12ec87SRichard W.M. Jones 
22546f5ac20SEric Blake     qdict_put_str(options, "path", uri->path);
2260a12ec87SRichard W.M. Jones 
2270a12ec87SRichard W.M. Jones     /* Pick out any query parameters that we understand, and ignore
2280a12ec87SRichard W.M. Jones      * the rest.
2290a12ec87SRichard W.M. Jones      */
2300a12ec87SRichard W.M. Jones     for (i = 0; i < qp->n; ++i) {
2310a12ec87SRichard W.M. Jones         if (strcmp(qp->p[i].name, "host_key_check") == 0) {
23246f5ac20SEric Blake             qdict_put_str(options, "host_key_check", qp->p[i].value);
2330a12ec87SRichard W.M. Jones         }
2340a12ec87SRichard W.M. Jones     }
2350a12ec87SRichard W.M. Jones 
2360a12ec87SRichard W.M. Jones     query_params_free(qp);
2370a12ec87SRichard W.M. Jones     uri_free(uri);
2380a12ec87SRichard W.M. Jones     return 0;
2390a12ec87SRichard W.M. Jones 
2400a12ec87SRichard W.M. Jones  err:
2410a12ec87SRichard W.M. Jones     uri_free(uri);
2420a12ec87SRichard W.M. Jones     return -EINVAL;
2430a12ec87SRichard W.M. Jones }
2440a12ec87SRichard W.M. Jones 
ssh_has_filename_options_conflict(QDict * options,Error ** errp)24589dbe180SAshijeet Acharya static bool ssh_has_filename_options_conflict(QDict *options, Error **errp)
24689dbe180SAshijeet Acharya {
24789dbe180SAshijeet Acharya     const QDictEntry *qe;
24889dbe180SAshijeet Acharya 
24989dbe180SAshijeet Acharya     for (qe = qdict_first(options); qe; qe = qdict_next(options, qe)) {
25089dbe180SAshijeet Acharya         if (!strcmp(qe->key, "host") ||
25189dbe180SAshijeet Acharya             !strcmp(qe->key, "port") ||
25289dbe180SAshijeet Acharya             !strcmp(qe->key, "path") ||
25389dbe180SAshijeet Acharya             !strcmp(qe->key, "user") ||
2540da5b8efSAshijeet Acharya             !strcmp(qe->key, "host_key_check") ||
2550da5b8efSAshijeet Acharya             strstart(qe->key, "server.", NULL))
25689dbe180SAshijeet Acharya         {
25789dbe180SAshijeet Acharya             error_setg(errp, "Option '%s' cannot be used with a file name",
25889dbe180SAshijeet Acharya                        qe->key);
25989dbe180SAshijeet Acharya             return true;
26089dbe180SAshijeet Acharya         }
26189dbe180SAshijeet Acharya     }
26289dbe180SAshijeet Acharya 
26389dbe180SAshijeet Acharya     return false;
26489dbe180SAshijeet Acharya }
26589dbe180SAshijeet Acharya 
ssh_parse_filename(const char * filename,QDict * options,Error ** errp)2660a12ec87SRichard W.M. Jones static void ssh_parse_filename(const char *filename, QDict *options,
2670a12ec87SRichard W.M. Jones                                Error **errp)
2680a12ec87SRichard W.M. Jones {
26989dbe180SAshijeet Acharya     if (ssh_has_filename_options_conflict(options, errp)) {
2700a12ec87SRichard W.M. Jones         return;
2710a12ec87SRichard W.M. Jones     }
2720a12ec87SRichard W.M. Jones 
2730a12ec87SRichard W.M. Jones     parse_uri(filename, options, errp);
2740a12ec87SRichard W.M. Jones }
2750a12ec87SRichard W.M. Jones 
check_host_key_knownhosts(BDRVSSHState * s,Error ** errp)276b10d49d7SPino Toscano static int check_host_key_knownhosts(BDRVSSHState *s, Error **errp)
2770a12ec87SRichard W.M. Jones {
278b10d49d7SPino Toscano     int ret;
279b10d49d7SPino Toscano     enum ssh_known_hosts_e state;
280b10d49d7SPino Toscano     int r;
281b10d49d7SPino Toscano     ssh_key pubkey;
282b10d49d7SPino Toscano     enum ssh_keytypes_e pubkey_type;
283b10d49d7SPino Toscano     unsigned char *server_hash = NULL;
284b10d49d7SPino Toscano     size_t server_hash_len;
285b10d49d7SPino Toscano     char *fingerprint = NULL;
2860a12ec87SRichard W.M. Jones 
287b10d49d7SPino Toscano     state = ssh_session_is_known_server(s->session);
288b10d49d7SPino Toscano     trace_ssh_server_status(state);
2890a12ec87SRichard W.M. Jones 
290b10d49d7SPino Toscano     switch (state) {
291b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_OK:
2920a12ec87SRichard W.M. Jones         /* OK */
293b10d49d7SPino Toscano         trace_ssh_check_host_key_knownhosts();
2940a12ec87SRichard W.M. Jones         break;
295b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_CHANGED:
2960a12ec87SRichard W.M. Jones         ret = -EINVAL;
297b10d49d7SPino Toscano         r = ssh_get_server_publickey(s->session, &pubkey);
298b10d49d7SPino Toscano         if (r == 0) {
299b10d49d7SPino Toscano             r = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256,
300b10d49d7SPino Toscano                                        &server_hash, &server_hash_len);
301b10d49d7SPino Toscano             pubkey_type = ssh_key_type(pubkey);
302b10d49d7SPino Toscano             ssh_key_free(pubkey);
303b10d49d7SPino Toscano         }
304b10d49d7SPino Toscano         if (r == 0) {
305b10d49d7SPino Toscano             fingerprint = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256,
306b10d49d7SPino Toscano                                                    server_hash,
307b10d49d7SPino Toscano                                                    server_hash_len);
308b10d49d7SPino Toscano             ssh_clean_pubkey_hash(&server_hash);
309b10d49d7SPino Toscano         }
310b10d49d7SPino Toscano         if (fingerprint) {
311b10d49d7SPino Toscano             error_setg(errp,
312b10d49d7SPino Toscano                        "host key (%s key with fingerprint %s) does not match "
313b10d49d7SPino Toscano                        "the one in known_hosts; this may be a possible attack",
314b10d49d7SPino Toscano                        ssh_key_type_to_char(pubkey_type), fingerprint);
315b10d49d7SPino Toscano             ssh_string_free_char(fingerprint);
316b10d49d7SPino Toscano         } else  {
317b10d49d7SPino Toscano             error_setg(errp,
318b10d49d7SPino Toscano                        "host key does not match the one in known_hosts; this "
319b10d49d7SPino Toscano                        "may be a possible attack");
320b10d49d7SPino Toscano         }
3210a12ec87SRichard W.M. Jones         goto out;
322b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_OTHER:
3230a12ec87SRichard W.M. Jones         ret = -EINVAL;
324b10d49d7SPino Toscano         error_setg(errp,
325b10d49d7SPino Toscano                    "host key for this server not found, another type exists");
3260a12ec87SRichard W.M. Jones         goto out;
327b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_UNKNOWN:
3280a12ec87SRichard W.M. Jones         ret = -EINVAL;
329b10d49d7SPino Toscano         error_setg(errp, "no host key was found in known_hosts");
330b10d49d7SPino Toscano         goto out;
331b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_NOT_FOUND:
332b10d49d7SPino Toscano         ret = -ENOENT;
333b10d49d7SPino Toscano         error_setg(errp, "known_hosts file not found");
334b10d49d7SPino Toscano         goto out;
335b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_ERROR:
336b10d49d7SPino Toscano         ret = -EINVAL;
337b10d49d7SPino Toscano         error_setg(errp, "error while checking the host");
3380a12ec87SRichard W.M. Jones         goto out;
3390a12ec87SRichard W.M. Jones     default:
3400a12ec87SRichard W.M. Jones         ret = -EINVAL;
341b10d49d7SPino Toscano         error_setg(errp, "error while checking for known server (%d)", state);
3420a12ec87SRichard W.M. Jones         goto out;
3430a12ec87SRichard W.M. Jones     }
3440a12ec87SRichard W.M. Jones 
3450a12ec87SRichard W.M. Jones     /* known_hosts checking successful. */
3460a12ec87SRichard W.M. Jones     ret = 0;
3470a12ec87SRichard W.M. Jones 
3480a12ec87SRichard W.M. Jones  out:
3490a12ec87SRichard W.M. Jones     return ret;
3500a12ec87SRichard W.M. Jones }
3510a12ec87SRichard W.M. Jones 
hex2decimal(char ch)3520a12ec87SRichard W.M. Jones static unsigned hex2decimal(char ch)
3530a12ec87SRichard W.M. Jones {
3540a12ec87SRichard W.M. Jones     if (ch >= '0' && ch <= '9') {
3550a12ec87SRichard W.M. Jones         return (ch - '0');
3560a12ec87SRichard W.M. Jones     } else if (ch >= 'a' && ch <= 'f') {
3570a12ec87SRichard W.M. Jones         return 10 + (ch - 'a');
3580a12ec87SRichard W.M. Jones     } else if (ch >= 'A' && ch <= 'F') {
3590a12ec87SRichard W.M. Jones         return 10 + (ch - 'A');
3600a12ec87SRichard W.M. Jones     }
3610a12ec87SRichard W.M. Jones 
3620a12ec87SRichard W.M. Jones     return -1;
3630a12ec87SRichard W.M. Jones }
3640a12ec87SRichard W.M. Jones 
3650a12ec87SRichard W.M. Jones /* Compare the binary fingerprint (hash of host key) with the
3660a12ec87SRichard W.M. Jones  * host_key_check parameter.
3670a12ec87SRichard W.M. Jones  */
compare_fingerprint(const unsigned char * fingerprint,size_t len,const char * host_key_check)3680a12ec87SRichard W.M. Jones static int compare_fingerprint(const unsigned char *fingerprint, size_t len,
3690a12ec87SRichard W.M. Jones                                const char *host_key_check)
3700a12ec87SRichard W.M. Jones {
3710a12ec87SRichard W.M. Jones     unsigned c;
3720a12ec87SRichard W.M. Jones 
3730a12ec87SRichard W.M. Jones     while (len > 0) {
3740a12ec87SRichard W.M. Jones         while (*host_key_check == ':')
3750a12ec87SRichard W.M. Jones             host_key_check++;
3760a12ec87SRichard W.M. Jones         if (!qemu_isxdigit(host_key_check[0]) ||
3770a12ec87SRichard W.M. Jones             !qemu_isxdigit(host_key_check[1]))
3780a12ec87SRichard W.M. Jones             return 1;
3790a12ec87SRichard W.M. Jones         c = hex2decimal(host_key_check[0]) * 16 +
3800a12ec87SRichard W.M. Jones             hex2decimal(host_key_check[1]);
3810a12ec87SRichard W.M. Jones         if (c - *fingerprint != 0)
3820a12ec87SRichard W.M. Jones             return c - *fingerprint;
3830a12ec87SRichard W.M. Jones         fingerprint++;
3840a12ec87SRichard W.M. Jones         len--;
3850a12ec87SRichard W.M. Jones         host_key_check += 2;
3860a12ec87SRichard W.M. Jones     }
3870a12ec87SRichard W.M. Jones     return *host_key_check - '\0';
3880a12ec87SRichard W.M. Jones }
3890a12ec87SRichard W.M. Jones 
format_fingerprint(const unsigned char * fingerprint,size_t len)390e3296cc7SDaniel P. Berrangé static char *format_fingerprint(const unsigned char *fingerprint, size_t len)
391e3296cc7SDaniel P. Berrangé {
392e3296cc7SDaniel P. Berrangé     static const char *hex = "0123456789abcdef";
393e3296cc7SDaniel P. Berrangé     char *ret = g_new0(char, (len * 2) + 1);
394e3296cc7SDaniel P. Berrangé     for (size_t i = 0; i < len; i++) {
395e3296cc7SDaniel P. Berrangé         ret[i * 2] = hex[((fingerprint[i] >> 4) & 0xf)];
396e3296cc7SDaniel P. Berrangé         ret[(i * 2) + 1] = hex[(fingerprint[i] & 0xf)];
397e3296cc7SDaniel P. Berrangé     }
398e3296cc7SDaniel P. Berrangé     ret[len * 2] = '\0';
399e3296cc7SDaniel P. Berrangé     return ret;
400e3296cc7SDaniel P. Berrangé }
401e3296cc7SDaniel P. Berrangé 
4020a12ec87SRichard W.M. Jones static int
check_host_key_hash(BDRVSSHState * s,const char * hash,enum ssh_publickey_hash_type type,const char * typestr,Error ** errp)4030a12ec87SRichard W.M. Jones check_host_key_hash(BDRVSSHState *s, const char *hash,
404e3296cc7SDaniel P. Berrangé                     enum ssh_publickey_hash_type type, const char *typestr,
405e3296cc7SDaniel P. Berrangé                     Error **errp)
4060a12ec87SRichard W.M. Jones {
407b10d49d7SPino Toscano     int r;
408b10d49d7SPino Toscano     ssh_key pubkey;
409b10d49d7SPino Toscano     unsigned char *server_hash;
410b10d49d7SPino Toscano     size_t server_hash_len;
411e3296cc7SDaniel P. Berrangé     const char *keytype;
4120a12ec87SRichard W.M. Jones 
413b10d49d7SPino Toscano     r = ssh_get_server_publickey(s->session, &pubkey);
414b10d49d7SPino Toscano     if (r != SSH_OK) {
41501c2b265SMarkus Armbruster         session_error_setg(errp, s, "failed to read remote host key");
4160a12ec87SRichard W.M. Jones         return -EINVAL;
4170a12ec87SRichard W.M. Jones     }
4180a12ec87SRichard W.M. Jones 
419e3296cc7SDaniel P. Berrangé     keytype = ssh_key_type_to_char(ssh_key_type(pubkey));
420e3296cc7SDaniel P. Berrangé 
421b10d49d7SPino Toscano     r = ssh_get_publickey_hash(pubkey, type, &server_hash, &server_hash_len);
422b10d49d7SPino Toscano     ssh_key_free(pubkey);
423b10d49d7SPino Toscano     if (r != 0) {
424b10d49d7SPino Toscano         session_error_setg(errp, s,
425b10d49d7SPino Toscano                            "failed reading the hash of the server SSH key");
426b10d49d7SPino Toscano         return -EINVAL;
427b10d49d7SPino Toscano     }
428b10d49d7SPino Toscano 
429b10d49d7SPino Toscano     r = compare_fingerprint(server_hash, server_hash_len, hash);
430b10d49d7SPino Toscano     if (r != 0) {
431e3296cc7SDaniel P. Berrangé         g_autofree char *server_fp = format_fingerprint(server_hash,
432e3296cc7SDaniel P. Berrangé                                                         server_hash_len);
433e3296cc7SDaniel P. Berrangé         error_setg(errp, "remote host %s key fingerprint '%s:%s' "
434e3296cc7SDaniel P. Berrangé                    "does not match host_key_check '%s:%s'",
435e3296cc7SDaniel P. Berrangé                    keytype, typestr, server_fp, typestr, hash);
436e3296cc7SDaniel P. Berrangé         ssh_clean_pubkey_hash(&server_hash);
4370a12ec87SRichard W.M. Jones         return -EPERM;
4380a12ec87SRichard W.M. Jones     }
439e3296cc7SDaniel P. Berrangé     ssh_clean_pubkey_hash(&server_hash);
4400a12ec87SRichard W.M. Jones 
4410a12ec87SRichard W.M. Jones     return 0;
4420a12ec87SRichard W.M. Jones }
4430a12ec87SRichard W.M. Jones 
check_host_key(BDRVSSHState * s,SshHostKeyCheck * hkc,Error ** errp)444b10d49d7SPino Toscano static int check_host_key(BDRVSSHState *s, SshHostKeyCheck *hkc, Error **errp)
4450a12ec87SRichard W.M. Jones {
446ec2f5418SKevin Wolf     SshHostKeyCheckMode mode;
447ec2f5418SKevin Wolf 
448ec2f5418SKevin Wolf     if (hkc) {
449ec2f5418SKevin Wolf         mode = hkc->mode;
450ec2f5418SKevin Wolf     } else {
451ec2f5418SKevin Wolf         mode = SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS;
452ec2f5418SKevin Wolf     }
453ec2f5418SKevin Wolf 
454ec2f5418SKevin Wolf     switch (mode) {
455ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_NONE:
4560a12ec87SRichard W.M. Jones         return 0;
457ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_HASH:
458ec2f5418SKevin Wolf         if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
459ec2f5418SKevin Wolf             return check_host_key_hash(s, hkc->u.hash.hash,
460e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_MD5, "md5",
461e3296cc7SDaniel P. Berrangé                                        errp);
462ec2f5418SKevin Wolf         } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
463ec2f5418SKevin Wolf             return check_host_key_hash(s, hkc->u.hash.hash,
464e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_SHA1, "sha1",
465e3296cc7SDaniel P. Berrangé                                        errp);
466bf783261SDaniel P. Berrangé         } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA256) {
467bf783261SDaniel P. Berrangé             return check_host_key_hash(s, hkc->u.hash.hash,
468e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_SHA256, "sha256",
469e3296cc7SDaniel P. Berrangé                                        errp);
4700a12ec87SRichard W.M. Jones         }
471ec2f5418SKevin Wolf         g_assert_not_reached();
472ec2f5418SKevin Wolf         break;
473ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS:
474b10d49d7SPino Toscano         return check_host_key_knownhosts(s, errp);
475ec2f5418SKevin Wolf     default:
476ec2f5418SKevin Wolf         g_assert_not_reached();
4770a12ec87SRichard W.M. Jones     }
4780a12ec87SRichard W.M. Jones 
4790a12ec87SRichard W.M. Jones     return -EINVAL;
4800a12ec87SRichard W.M. Jones }
4810a12ec87SRichard W.M. Jones 
authenticate(BDRVSSHState * s,Error ** errp)482b10d49d7SPino Toscano static int authenticate(BDRVSSHState *s, Error **errp)
4830a12ec87SRichard W.M. Jones {
4840a12ec87SRichard W.M. Jones     int r, ret;
485b10d49d7SPino Toscano     int method;
4860a12ec87SRichard W.M. Jones 
487b10d49d7SPino Toscano     /* Try to authenticate with the "none" method. */
488b10d49d7SPino Toscano     r = ssh_userauth_none(s->session, NULL);
489b10d49d7SPino Toscano     if (r == SSH_AUTH_ERROR) {
4900a12ec87SRichard W.M. Jones         ret = -EPERM;
491b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to authenticate using none "
492b10d49d7SPino Toscano                                     "authentication");
4930a12ec87SRichard W.M. Jones         goto out;
494b10d49d7SPino Toscano     } else if (r == SSH_AUTH_SUCCESS) {
4950a12ec87SRichard W.M. Jones         /* Authenticated! */
4960a12ec87SRichard W.M. Jones         ret = 0;
4970a12ec87SRichard W.M. Jones         goto out;
4980a12ec87SRichard W.M. Jones     }
499b10d49d7SPino Toscano 
500b10d49d7SPino Toscano     method = ssh_userauth_list(s->session, NULL);
501b10d49d7SPino Toscano     trace_ssh_auth_methods(method);
502b10d49d7SPino Toscano 
503b10d49d7SPino Toscano     /*
504b10d49d7SPino Toscano      * Try to authenticate with publickey, using the ssh-agent
505b10d49d7SPino Toscano      * if available.
506b10d49d7SPino Toscano      */
507b10d49d7SPino Toscano     if (method & SSH_AUTH_METHOD_PUBLICKEY) {
508b10d49d7SPino Toscano         r = ssh_userauth_publickey_auto(s->session, NULL, NULL);
509b10d49d7SPino Toscano         if (r == SSH_AUTH_ERROR) {
510b10d49d7SPino Toscano             ret = -EINVAL;
511b10d49d7SPino Toscano             session_error_setg(errp, s, "failed to authenticate using "
512b10d49d7SPino Toscano                                         "publickey authentication");
513b10d49d7SPino Toscano             goto out;
514b10d49d7SPino Toscano         } else if (r == SSH_AUTH_SUCCESS) {
515b10d49d7SPino Toscano             /* Authenticated! */
516b10d49d7SPino Toscano             ret = 0;
517b10d49d7SPino Toscano             goto out;
518b10d49d7SPino Toscano         }
5190a12ec87SRichard W.M. Jones     }
5200a12ec87SRichard W.M. Jones 
5210a12ec87SRichard W.M. Jones     ret = -EPERM;
5224618e658SMarkus Armbruster     error_setg(errp, "failed to authenticate using publickey authentication "
5230a12ec87SRichard W.M. Jones                "and the identities held by your ssh-agent");
5240a12ec87SRichard W.M. Jones 
5250a12ec87SRichard W.M. Jones  out:
5260a12ec87SRichard W.M. Jones     return ret;
5270a12ec87SRichard W.M. Jones }
5280a12ec87SRichard W.M. Jones 
5298a6a8089SMax Reitz static QemuOptsList ssh_runtime_opts = {
5308a6a8089SMax Reitz     .name = "ssh",
5318a6a8089SMax Reitz     .head = QTAILQ_HEAD_INITIALIZER(ssh_runtime_opts.head),
5328a6a8089SMax Reitz     .desc = {
5338a6a8089SMax Reitz         {
5348a6a8089SMax Reitz             .name = "host",
5358a6a8089SMax Reitz             .type = QEMU_OPT_STRING,
5368a6a8089SMax Reitz             .help = "Host to connect to",
5378a6a8089SMax Reitz         },
5388a6a8089SMax Reitz         {
5398a6a8089SMax Reitz             .name = "port",
5408a6a8089SMax Reitz             .type = QEMU_OPT_NUMBER,
5418a6a8089SMax Reitz             .help = "Port to connect to",
5428a6a8089SMax Reitz         },
543ec2f5418SKevin Wolf         {
544ec2f5418SKevin Wolf             .name = "host_key_check",
545ec2f5418SKevin Wolf             .type = QEMU_OPT_STRING,
546ec2f5418SKevin Wolf             .help = "Defines how and what to check the host key against",
547ec2f5418SKevin Wolf         },
548fbd5c4c0SMurilo Opsfelder Araujo         { /* end of list */ }
5498a6a8089SMax Reitz     },
5508a6a8089SMax Reitz };
5518a6a8089SMax Reitz 
ssh_process_legacy_options(QDict * output_opts,QemuOpts * legacy_opts,Error ** errp)552ec2f5418SKevin Wolf static bool ssh_process_legacy_options(QDict *output_opts,
5530da5b8efSAshijeet Acharya                                        QemuOpts *legacy_opts,
5540da5b8efSAshijeet Acharya                                        Error **errp)
5550da5b8efSAshijeet Acharya {
5560da5b8efSAshijeet Acharya     const char *host = qemu_opt_get(legacy_opts, "host");
5570da5b8efSAshijeet Acharya     const char *port = qemu_opt_get(legacy_opts, "port");
558ec2f5418SKevin Wolf     const char *host_key_check = qemu_opt_get(legacy_opts, "host_key_check");
5590da5b8efSAshijeet Acharya 
5600da5b8efSAshijeet Acharya     if (!host && port) {
5610da5b8efSAshijeet Acharya         error_setg(errp, "port may not be used without host");
5620da5b8efSAshijeet Acharya         return false;
5630da5b8efSAshijeet Acharya     }
5640da5b8efSAshijeet Acharya 
5650da5b8efSAshijeet Acharya     if (host) {
56646f5ac20SEric Blake         qdict_put_str(output_opts, "server.host", host);
56746f5ac20SEric Blake         qdict_put_str(output_opts, "server.port", port ?: stringify(22));
5680da5b8efSAshijeet Acharya     }
5690da5b8efSAshijeet Acharya 
570ec2f5418SKevin Wolf     if (host_key_check) {
571ec2f5418SKevin Wolf         if (strcmp(host_key_check, "no") == 0) {
572ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "none");
573ec2f5418SKevin Wolf         } else if (strncmp(host_key_check, "md5:", 4) == 0) {
574ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "hash");
575ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.type", "md5");
576ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.hash",
577ec2f5418SKevin Wolf                           &host_key_check[4]);
578ec2f5418SKevin Wolf         } else if (strncmp(host_key_check, "sha1:", 5) == 0) {
579ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "hash");
580ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.type", "sha1");
581ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.hash",
582ec2f5418SKevin Wolf                           &host_key_check[5]);
583ea0f60e6SDaniel P. Berrangé         } else if (strncmp(host_key_check, "sha256:", 7) == 0) {
584ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.mode", "hash");
585ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.type", "sha256");
586ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.hash",
587ea0f60e6SDaniel P. Berrangé                           &host_key_check[7]);
588ec2f5418SKevin Wolf         } else if (strcmp(host_key_check, "yes") == 0) {
589ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "known_hosts");
590ec2f5418SKevin Wolf         } else {
591ec2f5418SKevin Wolf             error_setg(errp, "unknown host_key_check setting (%s)",
592ec2f5418SKevin Wolf                        host_key_check);
593ec2f5418SKevin Wolf             return false;
594ec2f5418SKevin Wolf         }
595ec2f5418SKevin Wolf     }
596ec2f5418SKevin Wolf 
5970da5b8efSAshijeet Acharya     return true;
5980da5b8efSAshijeet Acharya }
5990da5b8efSAshijeet Acharya 
ssh_parse_options(QDict * options,Error ** errp)60016e4bdb1SKevin Wolf static BlockdevOptionsSsh *ssh_parse_options(QDict *options, Error **errp)
6010da5b8efSAshijeet Acharya {
60216e4bdb1SKevin Wolf     BlockdevOptionsSsh *result = NULL;
60316e4bdb1SKevin Wolf     QemuOpts *opts = NULL;
60416e4bdb1SKevin Wolf     const QDictEntry *e;
60516e4bdb1SKevin Wolf     Visitor *v;
6060da5b8efSAshijeet Acharya 
60716e4bdb1SKevin Wolf     /* Translate legacy options */
60816e4bdb1SKevin Wolf     opts = qemu_opts_create(&ssh_runtime_opts, NULL, 0, &error_abort);
609af175e85SMarkus Armbruster     if (!qemu_opts_absorb_qdict(opts, options, errp)) {
61016e4bdb1SKevin Wolf         goto fail;
6110da5b8efSAshijeet Acharya     }
6120da5b8efSAshijeet Acharya 
613ec2f5418SKevin Wolf     if (!ssh_process_legacy_options(options, opts, errp)) {
61416e4bdb1SKevin Wolf         goto fail;
61516e4bdb1SKevin Wolf     }
61616e4bdb1SKevin Wolf 
61716e4bdb1SKevin Wolf     /* Create the QAPI object */
618af91062eSMarkus Armbruster     v = qobject_input_visitor_new_flat_confused(options, errp);
619af91062eSMarkus Armbruster     if (!v) {
62016e4bdb1SKevin Wolf         goto fail;
6210da5b8efSAshijeet Acharya     }
6220da5b8efSAshijeet Acharya 
623b11a093cSMarkus Armbruster     visit_type_BlockdevOptionsSsh(v, NULL, &result, errp);
62416e4bdb1SKevin Wolf     visit_free(v);
625b11a093cSMarkus Armbruster     if (!result) {
62616e4bdb1SKevin Wolf         goto fail;
6270da5b8efSAshijeet Acharya     }
6280da5b8efSAshijeet Acharya 
62916e4bdb1SKevin Wolf     /* Remove the processed options from the QDict (the visitor processes
63016e4bdb1SKevin Wolf      * _all_ options in the QDict) */
63116e4bdb1SKevin Wolf     while ((e = qdict_first(options))) {
63216e4bdb1SKevin Wolf         qdict_del(options, e->key);
63316e4bdb1SKevin Wolf     }
63416e4bdb1SKevin Wolf 
63516e4bdb1SKevin Wolf fail:
63616e4bdb1SKevin Wolf     qemu_opts_del(opts);
63716e4bdb1SKevin Wolf     return result;
6380da5b8efSAshijeet Acharya }
6390da5b8efSAshijeet Acharya 
connect_to_ssh(BDRVSSHState * s,BlockdevOptionsSsh * opts,int ssh_flags,int creat_mode,Error ** errp)640375f0b92SKevin Wolf static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
6415f0c39e5SMarkus Armbruster                           int ssh_flags, int creat_mode, Error **errp)
6420a12ec87SRichard W.M. Jones {
6430a12ec87SRichard W.M. Jones     int r, ret;
644b10d49d7SPino Toscano     unsigned int port = 0;
645b10d49d7SPino Toscano     int new_sock = -1;
6460a12ec87SRichard W.M. Jones 
64754fde4ffSMarkus Armbruster     if (opts->user) {
648b8c1f901SMax Reitz         s->user = g_strdup(opts->user);
64916e4bdb1SKevin Wolf     } else {
650b8c1f901SMax Reitz         s->user = g_strdup(g_get_user_name());
651b8c1f901SMax Reitz         if (!s->user) {
6525f0c39e5SMarkus Armbruster             error_setg_errno(errp, errno, "Can't get user name");
6530a12ec87SRichard W.M. Jones             ret = -errno;
6540a12ec87SRichard W.M. Jones             goto err;
6550a12ec87SRichard W.M. Jones         }
6560a12ec87SRichard W.M. Jones     }
6570a12ec87SRichard W.M. Jones 
6580da5b8efSAshijeet Acharya     /* Pop the config into our state object, Exit if invalid */
65916e4bdb1SKevin Wolf     s->inet = opts->server;
66016e4bdb1SKevin Wolf     opts->server = NULL;
6610da5b8efSAshijeet Acharya 
662b10d49d7SPino Toscano     if (qemu_strtoui(s->inet->port, NULL, 10, &port) < 0) {
6630da5b8efSAshijeet Acharya         error_setg(errp, "Use only numeric port value");
6640da5b8efSAshijeet Acharya         ret = -EINVAL;
6650da5b8efSAshijeet Acharya         goto err;
6660da5b8efSAshijeet Acharya     }
6679a2d462eSRichard W.M. Jones 
6680a12ec87SRichard W.M. Jones     /* Open the socket and connect. */
669b10d49d7SPino Toscano     new_sock = inet_connect_saddr(s->inet, errp);
670b10d49d7SPino Toscano     if (new_sock < 0) {
671325e3904SRichard W.M. Jones         ret = -EIO;
6720a12ec87SRichard W.M. Jones         goto err;
6730a12ec87SRichard W.M. Jones     }
6740a12ec87SRichard W.M. Jones 
675b10d49d7SPino Toscano     /*
676b10d49d7SPino Toscano      * Try to disable the Nagle algorithm on TCP sockets to reduce latency,
677b10d49d7SPino Toscano      * but do not fail if it cannot be disabled.
678b10d49d7SPino Toscano      */
679b10d49d7SPino Toscano     r = socket_set_nodelay(new_sock);
680b10d49d7SPino Toscano     if (r < 0) {
681b10d49d7SPino Toscano         warn_report("can't set TCP_NODELAY for the ssh server %s: %s",
682b10d49d7SPino Toscano                     s->inet->host, strerror(errno));
683b10d49d7SPino Toscano     }
684b10d49d7SPino Toscano 
6850a12ec87SRichard W.M. Jones     /* Create SSH session. */
686b10d49d7SPino Toscano     s->session = ssh_new();
6870a12ec87SRichard W.M. Jones     if (!s->session) {
6880a12ec87SRichard W.M. Jones         ret = -EINVAL;
689b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to initialize libssh session");
6900a12ec87SRichard W.M. Jones         goto err;
6910a12ec87SRichard W.M. Jones     }
6920a12ec87SRichard W.M. Jones 
693b10d49d7SPino Toscano     /*
694b10d49d7SPino Toscano      * Make sure we are in blocking mode during the connection and
695b10d49d7SPino Toscano      * authentication phases.
696b10d49d7SPino Toscano      */
697b10d49d7SPino Toscano     ssh_set_blocking(s->session, 1);
6980a12ec87SRichard W.M. Jones 
699b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_USER, s->user);
700b10d49d7SPino Toscano     if (r < 0) {
701b10d49d7SPino Toscano         ret = -EINVAL;
702b10d49d7SPino Toscano         session_error_setg(errp, s,
703b10d49d7SPino Toscano                            "failed to set the user in the libssh session");
704b10d49d7SPino Toscano         goto err;
705b10d49d7SPino Toscano     }
706b10d49d7SPino Toscano 
707b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_HOST, s->inet->host);
708b10d49d7SPino Toscano     if (r < 0) {
709b10d49d7SPino Toscano         ret = -EINVAL;
710b10d49d7SPino Toscano         session_error_setg(errp, s,
711b10d49d7SPino Toscano                            "failed to set the host in the libssh session");
712b10d49d7SPino Toscano         goto err;
713b10d49d7SPino Toscano     }
714b10d49d7SPino Toscano 
715b10d49d7SPino Toscano     if (port > 0) {
716b10d49d7SPino Toscano         r = ssh_options_set(s->session, SSH_OPTIONS_PORT, &port);
717b10d49d7SPino Toscano         if (r < 0) {
718b10d49d7SPino Toscano             ret = -EINVAL;
719b10d49d7SPino Toscano             session_error_setg(errp, s,
720b10d49d7SPino Toscano                                "failed to set the port in the libssh session");
721b10d49d7SPino Toscano             goto err;
722b10d49d7SPino Toscano         }
723b10d49d7SPino Toscano     }
724b10d49d7SPino Toscano 
725b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_COMPRESSION, "none");
726b10d49d7SPino Toscano     if (r < 0) {
727b10d49d7SPino Toscano         ret = -EINVAL;
728b10d49d7SPino Toscano         session_error_setg(errp, s,
729b10d49d7SPino Toscano                            "failed to disable the compression in the libssh "
730b10d49d7SPino Toscano                            "session");
731b10d49d7SPino Toscano         goto err;
732b10d49d7SPino Toscano     }
733b10d49d7SPino Toscano 
734b10d49d7SPino Toscano     /* Read ~/.ssh/config. */
735b10d49d7SPino Toscano     r = ssh_options_parse_config(s->session, NULL);
736b10d49d7SPino Toscano     if (r < 0) {
737b10d49d7SPino Toscano         ret = -EINVAL;
738b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to parse ~/.ssh/config");
739b10d49d7SPino Toscano         goto err;
740b10d49d7SPino Toscano     }
741b10d49d7SPino Toscano 
742b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_FD, &new_sock);
743b10d49d7SPino Toscano     if (r < 0) {
744b10d49d7SPino Toscano         ret = -EINVAL;
745b10d49d7SPino Toscano         session_error_setg(errp, s,
746b10d49d7SPino Toscano                            "failed to set the socket in the libssh session");
747b10d49d7SPino Toscano         goto err;
748b10d49d7SPino Toscano     }
749b10d49d7SPino Toscano     /* libssh took ownership of the socket. */
750b10d49d7SPino Toscano     s->sock = new_sock;
751b10d49d7SPino Toscano     new_sock = -1;
752b10d49d7SPino Toscano 
753b10d49d7SPino Toscano     /* Connect. */
754b10d49d7SPino Toscano     r = ssh_connect(s->session);
755b10d49d7SPino Toscano     if (r != SSH_OK) {
7560a12ec87SRichard W.M. Jones         ret = -EINVAL;
7575f0c39e5SMarkus Armbruster         session_error_setg(errp, s, "failed to establish SSH session");
7580a12ec87SRichard W.M. Jones         goto err;
7590a12ec87SRichard W.M. Jones     }
7600a12ec87SRichard W.M. Jones 
7610a12ec87SRichard W.M. Jones     /* Check the remote host's key against known_hosts. */
762b10d49d7SPino Toscano     ret = check_host_key(s, opts->host_key_check, errp);
7630a12ec87SRichard W.M. Jones     if (ret < 0) {
7640a12ec87SRichard W.M. Jones         goto err;
7650a12ec87SRichard W.M. Jones     }
7660a12ec87SRichard W.M. Jones 
7670a12ec87SRichard W.M. Jones     /* Authenticate. */
768b10d49d7SPino Toscano     ret = authenticate(s, errp);
7690a12ec87SRichard W.M. Jones     if (ret < 0) {
7700a12ec87SRichard W.M. Jones         goto err;
7710a12ec87SRichard W.M. Jones     }
7720a12ec87SRichard W.M. Jones 
7730a12ec87SRichard W.M. Jones     /* Start SFTP. */
774b10d49d7SPino Toscano     s->sftp = sftp_new(s->session);
7750a12ec87SRichard W.M. Jones     if (!s->sftp) {
776b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to create sftp handle");
777b10d49d7SPino Toscano         ret = -EINVAL;
778b10d49d7SPino Toscano         goto err;
779b10d49d7SPino Toscano     }
780b10d49d7SPino Toscano 
781b10d49d7SPino Toscano     r = sftp_init(s->sftp);
782b10d49d7SPino Toscano     if (r < 0) {
783b10d49d7SPino Toscano         sftp_error_setg(errp, s, "failed to initialize sftp handle");
7840a12ec87SRichard W.M. Jones         ret = -EINVAL;
7850a12ec87SRichard W.M. Jones         goto err;
7860a12ec87SRichard W.M. Jones     }
7870a12ec87SRichard W.M. Jones 
7880a12ec87SRichard W.M. Jones     /* Open the remote file. */
789023908a2SLaurent Vivier     trace_ssh_connect_to_ssh(opts->path, ssh_flags, creat_mode);
790b10d49d7SPino Toscano     s->sftp_handle = sftp_open(s->sftp, opts->path, ssh_flags, creat_mode);
7910a12ec87SRichard W.M. Jones     if (!s->sftp_handle) {
792b10d49d7SPino Toscano         sftp_error_setg(errp, s, "failed to open remote file '%s'",
79316e4bdb1SKevin Wolf                         opts->path);
7940a12ec87SRichard W.M. Jones         ret = -EINVAL;
7950a12ec87SRichard W.M. Jones         goto err;
7960a12ec87SRichard W.M. Jones     }
7970a12ec87SRichard W.M. Jones 
798b10d49d7SPino Toscano     /* Make sure the SFTP file is handled in blocking mode. */
799b10d49d7SPino Toscano     sftp_file_set_blocking(s->sftp_handle);
800b10d49d7SPino Toscano 
801b10d49d7SPino Toscano     s->attrs = sftp_fstat(s->sftp_handle);
802b10d49d7SPino Toscano     if (!s->attrs) {
8035496fb1aSMarkus Armbruster         sftp_error_setg(errp, s, "failed to read file attributes");
8040a12ec87SRichard W.M. Jones         return -EINVAL;
8050a12ec87SRichard W.M. Jones     }
8060a12ec87SRichard W.M. Jones 
8070a12ec87SRichard W.M. Jones     return 0;
8080a12ec87SRichard W.M. Jones 
8090a12ec87SRichard W.M. Jones  err:
810b10d49d7SPino Toscano     if (s->attrs) {
811b10d49d7SPino Toscano         sftp_attributes_free(s->attrs);
812b10d49d7SPino Toscano     }
813b10d49d7SPino Toscano     s->attrs = NULL;
8140a12ec87SRichard W.M. Jones     if (s->sftp_handle) {
815b10d49d7SPino Toscano         sftp_close(s->sftp_handle);
8160a12ec87SRichard W.M. Jones     }
8170a12ec87SRichard W.M. Jones     s->sftp_handle = NULL;
8180a12ec87SRichard W.M. Jones     if (s->sftp) {
819b10d49d7SPino Toscano         sftp_free(s->sftp);
8200a12ec87SRichard W.M. Jones     }
8210a12ec87SRichard W.M. Jones     s->sftp = NULL;
8220a12ec87SRichard W.M. Jones     if (s->session) {
823b10d49d7SPino Toscano         ssh_disconnect(s->session);
824b10d49d7SPino Toscano         ssh_free(s->session);
8250a12ec87SRichard W.M. Jones     }
8260a12ec87SRichard W.M. Jones     s->session = NULL;
827b10d49d7SPino Toscano     s->sock = -1;
828b10d49d7SPino Toscano     if (new_sock >= 0) {
829b10d49d7SPino Toscano         close(new_sock);
830b10d49d7SPino Toscano     }
8310a12ec87SRichard W.M. Jones 
8320a12ec87SRichard W.M. Jones     return ret;
8330a12ec87SRichard W.M. Jones }
8340a12ec87SRichard W.M. Jones 
ssh_file_open(BlockDriverState * bs,QDict * options,int bdrv_flags,Error ** errp)835015a1036SMax Reitz static int ssh_file_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
836015a1036SMax Reitz                          Error **errp)
8370a12ec87SRichard W.M. Jones {
8380a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
839375f0b92SKevin Wolf     BlockdevOptionsSsh *opts;
8400a12ec87SRichard W.M. Jones     int ret;
8410a12ec87SRichard W.M. Jones     int ssh_flags;
8420a12ec87SRichard W.M. Jones 
8430a12ec87SRichard W.M. Jones     ssh_state_init(s);
8440a12ec87SRichard W.M. Jones 
845b10d49d7SPino Toscano     ssh_flags = 0;
8460a12ec87SRichard W.M. Jones     if (bdrv_flags & BDRV_O_RDWR) {
847b10d49d7SPino Toscano         ssh_flags |= O_RDWR;
848b10d49d7SPino Toscano     } else {
849b10d49d7SPino Toscano         ssh_flags |= O_RDONLY;
8500a12ec87SRichard W.M. Jones     }
8510a12ec87SRichard W.M. Jones 
852375f0b92SKevin Wolf     opts = ssh_parse_options(options, errp);
853375f0b92SKevin Wolf     if (opts == NULL) {
854375f0b92SKevin Wolf         return -EINVAL;
855375f0b92SKevin Wolf     }
856375f0b92SKevin Wolf 
8570a12ec87SRichard W.M. Jones     /* Start up SSH. */
858375f0b92SKevin Wolf     ret = connect_to_ssh(s, opts, ssh_flags, 0, errp);
8590a12ec87SRichard W.M. Jones     if (ret < 0) {
8600a12ec87SRichard W.M. Jones         goto err;
8610a12ec87SRichard W.M. Jones     }
8620a12ec87SRichard W.M. Jones 
8630a12ec87SRichard W.M. Jones     /* Go non-blocking. */
864b10d49d7SPino Toscano     ssh_set_blocking(s->session, 0);
8650a12ec87SRichard W.M. Jones 
866be9c9404SEric Blake     if (s->attrs->type == SSH_FILEXFER_TYPE_REGULAR) {
867be9c9404SEric Blake         bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;
868be9c9404SEric Blake     }
869be9c9404SEric Blake 
870375f0b92SKevin Wolf     qapi_free_BlockdevOptionsSsh(opts);
871375f0b92SKevin Wolf 
8720a12ec87SRichard W.M. Jones     return 0;
8730a12ec87SRichard W.M. Jones 
8740a12ec87SRichard W.M. Jones  err:
875375f0b92SKevin Wolf     qapi_free_BlockdevOptionsSsh(opts);
876375f0b92SKevin Wolf 
8770a12ec87SRichard W.M. Jones     return ret;
8780a12ec87SRichard W.M. Jones }
8790a12ec87SRichard W.M. Jones 
880bd8e0e32SMax Reitz /* Note: This is a blocking operation */
ssh_grow_file(BDRVSSHState * s,int64_t offset,Error ** errp)8812b12a756SMax Reitz static int ssh_grow_file(BDRVSSHState *s, int64_t offset, Error **errp)
8822b12a756SMax Reitz {
8832b12a756SMax Reitz     ssize_t ret;
8842b12a756SMax Reitz     char c[1] = { '\0' };
885b10d49d7SPino Toscano     int was_blocking = ssh_is_blocking(s->session);
8862b12a756SMax Reitz 
8872b12a756SMax Reitz     /* offset must be strictly greater than the current size so we do
8882b12a756SMax Reitz      * not overwrite anything */
889b10d49d7SPino Toscano     assert(offset > 0 && offset > s->attrs->size);
8902b12a756SMax Reitz 
891b10d49d7SPino Toscano     ssh_set_blocking(s->session, 1);
892bd8e0e32SMax Reitz 
893b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset - 1);
894b10d49d7SPino Toscano     ret = sftp_write(s->sftp_handle, c, 1);
895bd8e0e32SMax Reitz 
896b10d49d7SPino Toscano     ssh_set_blocking(s->session, was_blocking);
897bd8e0e32SMax Reitz 
8982b12a756SMax Reitz     if (ret < 0) {
8992b12a756SMax Reitz         sftp_error_setg(errp, s, "Failed to grow file");
9002b12a756SMax Reitz         return -EIO;
9012b12a756SMax Reitz     }
9022b12a756SMax Reitz 
903b10d49d7SPino Toscano     s->attrs->size = offset;
9042b12a756SMax Reitz     return 0;
9052b12a756SMax Reitz }
9062b12a756SMax Reitz 
907766181feSChunyan Liu static QemuOptsList ssh_create_opts = {
908766181feSChunyan Liu     .name = "ssh-create-opts",
909766181feSChunyan Liu     .head = QTAILQ_HEAD_INITIALIZER(ssh_create_opts.head),
910766181feSChunyan Liu     .desc = {
9110a12ec87SRichard W.M. Jones         {
9120a12ec87SRichard W.M. Jones             .name = BLOCK_OPT_SIZE,
913766181feSChunyan Liu             .type = QEMU_OPT_SIZE,
9140a12ec87SRichard W.M. Jones             .help = "Virtual disk size"
9150a12ec87SRichard W.M. Jones         },
916766181feSChunyan Liu         { /* end of list */ }
917766181feSChunyan Liu     }
9180a12ec87SRichard W.M. Jones };
9190a12ec87SRichard W.M. Jones 
ssh_co_create(BlockdevCreateOptions * options,Error ** errp)9204906da7eSKevin Wolf static int ssh_co_create(BlockdevCreateOptions *options, Error **errp)
9210a12ec87SRichard W.M. Jones {
9224906da7eSKevin Wolf     BlockdevCreateOptionsSsh *opts = &options->u.ssh;
9230a12ec87SRichard W.M. Jones     BDRVSSHState s;
9244906da7eSKevin Wolf     int ret;
9254906da7eSKevin Wolf 
9264906da7eSKevin Wolf     assert(options->driver == BLOCKDEV_DRIVER_SSH);
9270a12ec87SRichard W.M. Jones 
9280a12ec87SRichard W.M. Jones     ssh_state_init(&s);
9290a12ec87SRichard W.M. Jones 
9304906da7eSKevin Wolf     ret = connect_to_ssh(&s, opts->location,
931b10d49d7SPino Toscano                          O_RDWR | O_CREAT | O_TRUNC,
9325496fb1aSMarkus Armbruster                          0644, errp);
9334906da7eSKevin Wolf     if (ret < 0) {
9344906da7eSKevin Wolf         goto fail;
9350a12ec87SRichard W.M. Jones     }
9360a12ec87SRichard W.M. Jones 
9374906da7eSKevin Wolf     if (opts->size > 0) {
9384906da7eSKevin Wolf         ret = ssh_grow_file(&s, opts->size, errp);
9392b12a756SMax Reitz         if (ret < 0) {
9404906da7eSKevin Wolf             goto fail;
9410a12ec87SRichard W.M. Jones         }
9420a12ec87SRichard W.M. Jones     }
9430a12ec87SRichard W.M. Jones 
9440a12ec87SRichard W.M. Jones     ret = 0;
9454906da7eSKevin Wolf fail:
9464906da7eSKevin Wolf     ssh_state_free(&s);
9474906da7eSKevin Wolf     return ret;
9484906da7eSKevin Wolf }
9494906da7eSKevin Wolf 
ssh_co_create_opts(BlockDriver * drv,const char * filename,QemuOpts * opts,Error ** errp)950b92902dfSMaxim Levitsky static int coroutine_fn ssh_co_create_opts(BlockDriver *drv,
951b92902dfSMaxim Levitsky                                            const char *filename,
952b92902dfSMaxim Levitsky                                            QemuOpts *opts,
9534906da7eSKevin Wolf                                            Error **errp)
9544906da7eSKevin Wolf {
9554906da7eSKevin Wolf     BlockdevCreateOptions *create_options;
9564906da7eSKevin Wolf     BlockdevCreateOptionsSsh *ssh_opts;
9574906da7eSKevin Wolf     int ret;
9584906da7eSKevin Wolf     QDict *uri_options = NULL;
9594906da7eSKevin Wolf 
9604906da7eSKevin Wolf     create_options = g_new0(BlockdevCreateOptions, 1);
9614906da7eSKevin Wolf     create_options->driver = BLOCKDEV_DRIVER_SSH;
9624906da7eSKevin Wolf     ssh_opts = &create_options->u.ssh;
9634906da7eSKevin Wolf 
9644906da7eSKevin Wolf     /* Get desired file size. */
9654906da7eSKevin Wolf     ssh_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
9664906da7eSKevin Wolf                               BDRV_SECTOR_SIZE);
967023908a2SLaurent Vivier     trace_ssh_co_create_opts(ssh_opts->size);
9684906da7eSKevin Wolf 
9694906da7eSKevin Wolf     uri_options = qdict_new();
9704906da7eSKevin Wolf     ret = parse_uri(filename, uri_options, errp);
9714906da7eSKevin Wolf     if (ret < 0) {
9724906da7eSKevin Wolf         goto out;
9734906da7eSKevin Wolf     }
9744906da7eSKevin Wolf 
9754906da7eSKevin Wolf     ssh_opts->location = ssh_parse_options(uri_options, errp);
9764906da7eSKevin Wolf     if (ssh_opts->location == NULL) {
9774906da7eSKevin Wolf         ret = -EINVAL;
9784906da7eSKevin Wolf         goto out;
9794906da7eSKevin Wolf     }
9804906da7eSKevin Wolf 
9814906da7eSKevin Wolf     ret = ssh_co_create(create_options, errp);
9820a12ec87SRichard W.M. Jones 
9830a12ec87SRichard W.M. Jones  out:
984cb3e7f08SMarc-André Lureau     qobject_unref(uri_options);
9854906da7eSKevin Wolf     qapi_free_BlockdevCreateOptions(create_options);
9860a12ec87SRichard W.M. Jones     return ret;
9870a12ec87SRichard W.M. Jones }
9880a12ec87SRichard W.M. Jones 
ssh_close(BlockDriverState * bs)9890a12ec87SRichard W.M. Jones static void ssh_close(BlockDriverState *bs)
9900a12ec87SRichard W.M. Jones {
9910a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
9920a12ec87SRichard W.M. Jones 
9930a12ec87SRichard W.M. Jones     ssh_state_free(s);
9940a12ec87SRichard W.M. Jones }
9950a12ec87SRichard W.M. Jones 
ssh_has_zero_init(BlockDriverState * bs)9960b3f21e6SRichard W.M. Jones static int ssh_has_zero_init(BlockDriverState *bs)
9970b3f21e6SRichard W.M. Jones {
9980b3f21e6SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
9990b3f21e6SRichard W.M. Jones     /* Assume false, unless we can positively prove it's true. */
10000b3f21e6SRichard W.M. Jones     int has_zero_init = 0;
10010b3f21e6SRichard W.M. Jones 
1002b10d49d7SPino Toscano     if (s->attrs->type == SSH_FILEXFER_TYPE_REGULAR) {
10030b3f21e6SRichard W.M. Jones         has_zero_init = 1;
10040b3f21e6SRichard W.M. Jones     }
10050b3f21e6SRichard W.M. Jones 
10060b3f21e6SRichard W.M. Jones     return has_zero_init;
10070b3f21e6SRichard W.M. Jones }
10080b3f21e6SRichard W.M. Jones 
10095aca18a4SPaolo Bonzini typedef struct BDRVSSHRestart {
10105aca18a4SPaolo Bonzini     BlockDriverState *bs;
10115aca18a4SPaolo Bonzini     Coroutine *co;
10125aca18a4SPaolo Bonzini } BDRVSSHRestart;
10135aca18a4SPaolo Bonzini 
restart_coroutine(void * opaque)10140a12ec87SRichard W.M. Jones static void restart_coroutine(void *opaque)
10150a12ec87SRichard W.M. Jones {
10165aca18a4SPaolo Bonzini     BDRVSSHRestart *restart = opaque;
10175aca18a4SPaolo Bonzini     BlockDriverState *bs = restart->bs;
10185aca18a4SPaolo Bonzini     BDRVSSHState *s = bs->opaque;
10195aca18a4SPaolo Bonzini     AioContext *ctx = bdrv_get_aio_context(bs);
10200a12ec87SRichard W.M. Jones 
1021023908a2SLaurent Vivier     trace_ssh_restart_coroutine(restart->co);
1022*60f782b6SStefan Hajnoczi     aio_set_fd_handler(ctx, s->sock, NULL, NULL, NULL, NULL, NULL);
10230a12ec87SRichard W.M. Jones 
10245aca18a4SPaolo Bonzini     aio_co_wake(restart->co);
10250a12ec87SRichard W.M. Jones }
10260a12ec87SRichard W.M. Jones 
10279d456654SPaolo Bonzini /* A non-blocking call returned EAGAIN, so yield, ensuring the
10289d456654SPaolo Bonzini  * handlers are set up so that we'll be rescheduled when there is an
10299d456654SPaolo Bonzini  * interesting event on the socket.
10309d456654SPaolo Bonzini  */
co_yield(BDRVSSHState * s,BlockDriverState * bs)10319d456654SPaolo Bonzini static coroutine_fn void co_yield(BDRVSSHState *s, BlockDriverState *bs)
10320a12ec87SRichard W.M. Jones {
10330a12ec87SRichard W.M. Jones     int r;
10340a12ec87SRichard W.M. Jones     IOHandler *rd_handler = NULL, *wr_handler = NULL;
10355aca18a4SPaolo Bonzini     BDRVSSHRestart restart = {
10365aca18a4SPaolo Bonzini         .bs = bs,
10375aca18a4SPaolo Bonzini         .co = qemu_coroutine_self()
10385aca18a4SPaolo Bonzini     };
10390a12ec87SRichard W.M. Jones 
1040b10d49d7SPino Toscano     r = ssh_get_poll_flags(s->session);
10410a12ec87SRichard W.M. Jones 
1042b10d49d7SPino Toscano     if (r & SSH_READ_PENDING) {
10430a12ec87SRichard W.M. Jones         rd_handler = restart_coroutine;
10440a12ec87SRichard W.M. Jones     }
1045b10d49d7SPino Toscano     if (r & SSH_WRITE_PENDING) {
10460a12ec87SRichard W.M. Jones         wr_handler = restart_coroutine;
10470a12ec87SRichard W.M. Jones     }
10480a12ec87SRichard W.M. Jones 
1049023908a2SLaurent Vivier     trace_ssh_co_yield(s->sock, rd_handler, wr_handler);
10500a12ec87SRichard W.M. Jones 
10512af0b200SStefan Hajnoczi     aio_set_fd_handler(bdrv_get_aio_context(bs), s->sock,
1052*60f782b6SStefan Hajnoczi                        rd_handler, wr_handler, NULL, NULL, &restart);
10530a12ec87SRichard W.M. Jones     qemu_coroutine_yield();
1054023908a2SLaurent Vivier     trace_ssh_co_yield_back(s->sock);
10550a12ec87SRichard W.M. Jones }
10560a12ec87SRichard W.M. Jones 
ssh_read(BDRVSSHState * s,BlockDriverState * bs,int64_t offset,size_t size,QEMUIOVector * qiov)10572af0b200SStefan Hajnoczi static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
10580a12ec87SRichard W.M. Jones                                  int64_t offset, size_t size,
10590a12ec87SRichard W.M. Jones                                  QEMUIOVector *qiov)
10600a12ec87SRichard W.M. Jones {
10610a12ec87SRichard W.M. Jones     ssize_t r;
10620a12ec87SRichard W.M. Jones     size_t got;
10630a12ec87SRichard W.M. Jones     char *buf, *end_of_vec;
10640a12ec87SRichard W.M. Jones     struct iovec *i;
10650a12ec87SRichard W.M. Jones 
1066023908a2SLaurent Vivier     trace_ssh_read(offset, size);
10670a12ec87SRichard W.M. Jones 
1068b10d49d7SPino Toscano     trace_ssh_seek(offset);
1069b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset);
10700a12ec87SRichard W.M. Jones 
10710a12ec87SRichard W.M. Jones     /* This keeps track of the current iovec element ('i'), where we
10720a12ec87SRichard W.M. Jones      * will write to next ('buf'), and the end of the current iovec
10730a12ec87SRichard W.M. Jones      * ('end_of_vec').
10740a12ec87SRichard W.M. Jones      */
10750a12ec87SRichard W.M. Jones     i = &qiov->iov[0];
10760a12ec87SRichard W.M. Jones     buf = i->iov_base;
10770a12ec87SRichard W.M. Jones     end_of_vec = i->iov_base + i->iov_len;
10780a12ec87SRichard W.M. Jones 
10790a12ec87SRichard W.M. Jones     for (got = 0; got < size; ) {
1080b10d49d7SPino Toscano         size_t request_read_size;
10810a12ec87SRichard W.M. Jones     again:
1082b10d49d7SPino Toscano         /*
1083b10d49d7SPino Toscano          * The size of SFTP packets is limited to 32K bytes, so limit
1084b10d49d7SPino Toscano          * the amount of data requested to 16K, as libssh currently
1085b10d49d7SPino Toscano          * does not handle multiple requests on its own.
1086b10d49d7SPino Toscano          */
1087b10d49d7SPino Toscano         request_read_size = MIN(end_of_vec - buf, 16384);
1088b10d49d7SPino Toscano         trace_ssh_read_buf(buf, end_of_vec - buf, request_read_size);
1089b10d49d7SPino Toscano         r = sftp_read(s->sftp_handle, buf, request_read_size);
1090b10d49d7SPino Toscano         trace_ssh_read_return(r, sftp_get_error(s->sftp));
10910a12ec87SRichard W.M. Jones 
1092b10d49d7SPino Toscano         if (r == SSH_AGAIN) {
10932af0b200SStefan Hajnoczi             co_yield(s, bs);
10940a12ec87SRichard W.M. Jones             goto again;
10950a12ec87SRichard W.M. Jones         }
1096b10d49d7SPino Toscano         if (r == SSH_EOF || (r == 0 && sftp_get_error(s->sftp) == SSH_FX_EOF)) {
10970a12ec87SRichard W.M. Jones             /* EOF: Short read so pad the buffer with zeroes and return it. */
10980a12ec87SRichard W.M. Jones             qemu_iovec_memset(qiov, got, 0, size - got);
10990a12ec87SRichard W.M. Jones             return 0;
11000a12ec87SRichard W.M. Jones         }
1101b10d49d7SPino Toscano         if (r <= 0) {
1102b10d49d7SPino Toscano             sftp_error_trace(s, "read");
1103b10d49d7SPino Toscano             return -EIO;
1104b10d49d7SPino Toscano         }
11050a12ec87SRichard W.M. Jones 
11060a12ec87SRichard W.M. Jones         got += r;
11070a12ec87SRichard W.M. Jones         buf += r;
11080a12ec87SRichard W.M. Jones         if (buf >= end_of_vec && got < size) {
11090a12ec87SRichard W.M. Jones             i++;
11100a12ec87SRichard W.M. Jones             buf = i->iov_base;
11110a12ec87SRichard W.M. Jones             end_of_vec = i->iov_base + i->iov_len;
11120a12ec87SRichard W.M. Jones         }
11130a12ec87SRichard W.M. Jones     }
11140a12ec87SRichard W.M. Jones 
11150a12ec87SRichard W.M. Jones     return 0;
11160a12ec87SRichard W.M. Jones }
11170a12ec87SRichard W.M. Jones 
ssh_co_readv(BlockDriverState * bs,int64_t sector_num,int nb_sectors,QEMUIOVector * qiov)11180a12ec87SRichard W.M. Jones static coroutine_fn int ssh_co_readv(BlockDriverState *bs,
11190a12ec87SRichard W.M. Jones                                      int64_t sector_num,
11200a12ec87SRichard W.M. Jones                                      int nb_sectors, QEMUIOVector *qiov)
11210a12ec87SRichard W.M. Jones {
11220a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
11230a12ec87SRichard W.M. Jones     int ret;
11240a12ec87SRichard W.M. Jones 
11250a12ec87SRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
11262af0b200SStefan Hajnoczi     ret = ssh_read(s, bs, sector_num * BDRV_SECTOR_SIZE,
11270a12ec87SRichard W.M. Jones                    nb_sectors * BDRV_SECTOR_SIZE, qiov);
11280a12ec87SRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
11290a12ec87SRichard W.M. Jones 
11300a12ec87SRichard W.M. Jones     return ret;
11310a12ec87SRichard W.M. Jones }
11320a12ec87SRichard W.M. Jones 
ssh_write(BDRVSSHState * s,BlockDriverState * bs,int64_t offset,size_t size,QEMUIOVector * qiov)113342f6ad79SAlberto Faria static coroutine_fn int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
11340a12ec87SRichard W.M. Jones                                   int64_t offset, size_t size,
11350a12ec87SRichard W.M. Jones                                   QEMUIOVector *qiov)
11360a12ec87SRichard W.M. Jones {
11370a12ec87SRichard W.M. Jones     ssize_t r;
11380a12ec87SRichard W.M. Jones     size_t written;
11390a12ec87SRichard W.M. Jones     char *buf, *end_of_vec;
11400a12ec87SRichard W.M. Jones     struct iovec *i;
11410a12ec87SRichard W.M. Jones 
1142023908a2SLaurent Vivier     trace_ssh_write(offset, size);
11430a12ec87SRichard W.M. Jones 
1144b10d49d7SPino Toscano     trace_ssh_seek(offset);
1145b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset);
11460a12ec87SRichard W.M. Jones 
11470a12ec87SRichard W.M. Jones     /* This keeps track of the current iovec element ('i'), where we
11480a12ec87SRichard W.M. Jones      * will read from next ('buf'), and the end of the current iovec
11490a12ec87SRichard W.M. Jones      * ('end_of_vec').
11500a12ec87SRichard W.M. Jones      */
11510a12ec87SRichard W.M. Jones     i = &qiov->iov[0];
11520a12ec87SRichard W.M. Jones     buf = i->iov_base;
11530a12ec87SRichard W.M. Jones     end_of_vec = i->iov_base + i->iov_len;
11540a12ec87SRichard W.M. Jones 
11550a12ec87SRichard W.M. Jones     for (written = 0; written < size; ) {
1156b10d49d7SPino Toscano         size_t request_write_size;
11570a12ec87SRichard W.M. Jones     again:
1158b10d49d7SPino Toscano         /*
1159b10d49d7SPino Toscano          * Avoid too large data packets, as libssh currently does not
1160b10d49d7SPino Toscano          * handle multiple requests on its own.
1161b10d49d7SPino Toscano          */
1162b10d49d7SPino Toscano         request_write_size = MIN(end_of_vec - buf, 131072);
1163b10d49d7SPino Toscano         trace_ssh_write_buf(buf, end_of_vec - buf, request_write_size);
1164b10d49d7SPino Toscano         r = sftp_write(s->sftp_handle, buf, request_write_size);
1165b10d49d7SPino Toscano         trace_ssh_write_return(r, sftp_get_error(s->sftp));
11660a12ec87SRichard W.M. Jones 
1167b10d49d7SPino Toscano         if (r == SSH_AGAIN) {
11682af0b200SStefan Hajnoczi             co_yield(s, bs);
11690a12ec87SRichard W.M. Jones             goto again;
11700a12ec87SRichard W.M. Jones         }
11710a12ec87SRichard W.M. Jones         if (r < 0) {
11726b3048ceSMarkus Armbruster             sftp_error_trace(s, "write");
11730a12ec87SRichard W.M. Jones             return -EIO;
11740a12ec87SRichard W.M. Jones         }
11750a12ec87SRichard W.M. Jones 
11760a12ec87SRichard W.M. Jones         written += r;
11770a12ec87SRichard W.M. Jones         buf += r;
11780a12ec87SRichard W.M. Jones         if (buf >= end_of_vec && written < size) {
11790a12ec87SRichard W.M. Jones             i++;
11800a12ec87SRichard W.M. Jones             buf = i->iov_base;
11810a12ec87SRichard W.M. Jones             end_of_vec = i->iov_base + i->iov_len;
11820a12ec87SRichard W.M. Jones         }
11830a12ec87SRichard W.M. Jones 
1184b10d49d7SPino Toscano         if (offset + written > s->attrs->size) {
1185b10d49d7SPino Toscano             s->attrs->size = offset + written;
1186b10d49d7SPino Toscano         }
11870a12ec87SRichard W.M. Jones     }
11880a12ec87SRichard W.M. Jones 
11890a12ec87SRichard W.M. Jones     return 0;
11900a12ec87SRichard W.M. Jones }
11910a12ec87SRichard W.M. Jones 
ssh_co_writev(BlockDriverState * bs,int64_t sector_num,int nb_sectors,QEMUIOVector * qiov,int flags)11920a12ec87SRichard W.M. Jones static coroutine_fn int ssh_co_writev(BlockDriverState *bs,
11930a12ec87SRichard W.M. Jones                                       int64_t sector_num,
1194e18a58b4SEric Blake                                       int nb_sectors, QEMUIOVector *qiov,
1195e18a58b4SEric Blake                                       int flags)
11960a12ec87SRichard W.M. Jones {
11970a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
11980a12ec87SRichard W.M. Jones     int ret;
11990a12ec87SRichard W.M. Jones 
12000a12ec87SRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
12012af0b200SStefan Hajnoczi     ret = ssh_write(s, bs, sector_num * BDRV_SECTOR_SIZE,
12020a12ec87SRichard W.M. Jones                     nb_sectors * BDRV_SECTOR_SIZE, qiov);
12030a12ec87SRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
12040a12ec87SRichard W.M. Jones 
12050a12ec87SRichard W.M. Jones     return ret;
12060a12ec87SRichard W.M. Jones }
12070a12ec87SRichard W.M. Jones 
unsafe_flush_warning(BDRVSSHState * s,const char * what)12089a2d462eSRichard W.M. Jones static void unsafe_flush_warning(BDRVSSHState *s, const char *what)
12099a2d462eSRichard W.M. Jones {
12109a2d462eSRichard W.M. Jones     if (!s->unsafe_flush_warning) {
12113dc6f869SAlistair Francis         warn_report("ssh server %s does not support fsync",
12120da5b8efSAshijeet Acharya                     s->inet->host);
12139a2d462eSRichard W.M. Jones         if (what) {
12149a2d462eSRichard W.M. Jones             error_report("to support fsync, you need %s", what);
12159a2d462eSRichard W.M. Jones         }
12169a2d462eSRichard W.M. Jones         s->unsafe_flush_warning = true;
12179a2d462eSRichard W.M. Jones     }
12189a2d462eSRichard W.M. Jones }
12199a2d462eSRichard W.M. Jones 
ssh_flush(BDRVSSHState * s,BlockDriverState * bs)12202af0b200SStefan Hajnoczi static coroutine_fn int ssh_flush(BDRVSSHState *s, BlockDriverState *bs)
12219a2d462eSRichard W.M. Jones {
12229a2d462eSRichard W.M. Jones     int r;
12239a2d462eSRichard W.M. Jones 
1224023908a2SLaurent Vivier     trace_ssh_flush();
1225b10d49d7SPino Toscano 
1226b10d49d7SPino Toscano     if (!sftp_extension_supported(s->sftp, "fsync@openssh.com", "1")) {
12279a2d462eSRichard W.M. Jones         unsafe_flush_warning(s, "OpenSSH >= 6.3");
12289a2d462eSRichard W.M. Jones         return 0;
12299a2d462eSRichard W.M. Jones     }
1230b10d49d7SPino Toscano  again:
1231b10d49d7SPino Toscano     r = sftp_fsync(s->sftp_handle);
1232b10d49d7SPino Toscano     if (r == SSH_AGAIN) {
1233b10d49d7SPino Toscano         co_yield(s, bs);
1234b10d49d7SPino Toscano         goto again;
1235b10d49d7SPino Toscano     }
12369a2d462eSRichard W.M. Jones     if (r < 0) {
12376b3048ceSMarkus Armbruster         sftp_error_trace(s, "fsync");
12389a2d462eSRichard W.M. Jones         return -EIO;
12399a2d462eSRichard W.M. Jones     }
12409a2d462eSRichard W.M. Jones 
12419a2d462eSRichard W.M. Jones     return 0;
12429a2d462eSRichard W.M. Jones }
12439a2d462eSRichard W.M. Jones 
ssh_co_flush(BlockDriverState * bs)12449a2d462eSRichard W.M. Jones static coroutine_fn int ssh_co_flush(BlockDriverState *bs)
12459a2d462eSRichard W.M. Jones {
12469a2d462eSRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
12479a2d462eSRichard W.M. Jones     int ret;
12489a2d462eSRichard W.M. Jones 
12499a2d462eSRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
12502af0b200SStefan Hajnoczi     ret = ssh_flush(s, bs);
12519a2d462eSRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
12529a2d462eSRichard W.M. Jones 
12539a2d462eSRichard W.M. Jones     return ret;
12549a2d462eSRichard W.M. Jones }
12559a2d462eSRichard W.M. Jones 
ssh_co_getlength(BlockDriverState * bs)1256c86422c5SEmanuele Giuseppe Esposito static int64_t coroutine_fn ssh_co_getlength(BlockDriverState *bs)
12570a12ec87SRichard W.M. Jones {
12580a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
12590a12ec87SRichard W.M. Jones     int64_t length;
12600a12ec87SRichard W.M. Jones 
1261b10d49d7SPino Toscano     /* Note we cannot make a libssh call here. */
1262b10d49d7SPino Toscano     length = (int64_t) s->attrs->size;
1263023908a2SLaurent Vivier     trace_ssh_getlength(length);
12640a12ec87SRichard W.M. Jones 
12650a12ec87SRichard W.M. Jones     return length;
12660a12ec87SRichard W.M. Jones }
12670a12ec87SRichard W.M. Jones 
ssh_co_truncate(BlockDriverState * bs,int64_t offset,bool exact,PreallocMode prealloc,BdrvRequestFlags flags,Error ** errp)1268061ca8a3SKevin Wolf static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
1269c80d8b06SMax Reitz                                         bool exact, PreallocMode prealloc,
127092b92799SKevin Wolf                                         BdrvRequestFlags flags, Error **errp)
1271624f3006SMax Reitz {
1272624f3006SMax Reitz     BDRVSSHState *s = bs->opaque;
1273624f3006SMax Reitz 
1274624f3006SMax Reitz     if (prealloc != PREALLOC_MODE_OFF) {
1275624f3006SMax Reitz         error_setg(errp, "Unsupported preallocation mode '%s'",
1276624f3006SMax Reitz                    PreallocMode_str(prealloc));
1277624f3006SMax Reitz         return -ENOTSUP;
1278624f3006SMax Reitz     }
1279624f3006SMax Reitz 
1280b10d49d7SPino Toscano     if (offset < s->attrs->size) {
1281624f3006SMax Reitz         error_setg(errp, "ssh driver does not support shrinking files");
1282624f3006SMax Reitz         return -ENOTSUP;
1283624f3006SMax Reitz     }
1284624f3006SMax Reitz 
1285b10d49d7SPino Toscano     if (offset == s->attrs->size) {
1286624f3006SMax Reitz         return 0;
1287624f3006SMax Reitz     }
1288624f3006SMax Reitz 
1289624f3006SMax Reitz     return ssh_grow_file(s, offset, errp);
1290624f3006SMax Reitz }
1291624f3006SMax Reitz 
ssh_refresh_filename(BlockDriverState * bs)1292b8c1f901SMax Reitz static void ssh_refresh_filename(BlockDriverState *bs)
1293b8c1f901SMax Reitz {
1294b8c1f901SMax Reitz     BDRVSSHState *s = bs->opaque;
1295b8c1f901SMax Reitz     const char *path, *host_key_check;
1296b8c1f901SMax Reitz     int ret;
1297b8c1f901SMax Reitz 
1298b8c1f901SMax Reitz     /*
1299b8c1f901SMax Reitz      * None of these options can be represented in a plain "host:port"
1300b8c1f901SMax Reitz      * format, so if any was given, we have to abort.
1301b8c1f901SMax Reitz      */
1302b8c1f901SMax Reitz     if (s->inet->has_ipv4 || s->inet->has_ipv6 || s->inet->has_to ||
1303b8c1f901SMax Reitz         s->inet->has_numeric)
1304b8c1f901SMax Reitz     {
1305b8c1f901SMax Reitz         return;
1306b8c1f901SMax Reitz     }
1307b8c1f901SMax Reitz 
1308b8c1f901SMax Reitz     path = qdict_get_try_str(bs->full_open_options, "path");
1309b8c1f901SMax Reitz     assert(path); /* mandatory option */
1310b8c1f901SMax Reitz 
1311b8c1f901SMax Reitz     host_key_check = qdict_get_try_str(bs->full_open_options, "host_key_check");
1312b8c1f901SMax Reitz 
1313b8c1f901SMax Reitz     ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
1314b8c1f901SMax Reitz                    "ssh://%s@%s:%s%s%s%s",
1315b8c1f901SMax Reitz                    s->user, s->inet->host, s->inet->port, path,
1316b8c1f901SMax Reitz                    host_key_check ? "?host_key_check=" : "",
1317b8c1f901SMax Reitz                    host_key_check ?: "");
1318b8c1f901SMax Reitz     if (ret >= sizeof(bs->exact_filename)) {
1319b8c1f901SMax Reitz         /* An overflow makes the filename unusable, so do not report any */
1320b8c1f901SMax Reitz         bs->exact_filename[0] = '\0';
1321b8c1f901SMax Reitz     }
1322b8c1f901SMax Reitz }
1323b8c1f901SMax Reitz 
ssh_bdrv_dirname(BlockDriverState * bs,Error ** errp)132421205c7cSMax Reitz static char *ssh_bdrv_dirname(BlockDriverState *bs, Error **errp)
132521205c7cSMax Reitz {
132621205c7cSMax Reitz     if (qdict_haskey(bs->full_open_options, "host_key_check")) {
132721205c7cSMax Reitz         /*
132821205c7cSMax Reitz          * We cannot generate a simple prefix if we would have to
132921205c7cSMax Reitz          * append a query string.
133021205c7cSMax Reitz          */
133121205c7cSMax Reitz         error_setg(errp,
133221205c7cSMax Reitz                    "Cannot generate a base directory with host_key_check set");
133321205c7cSMax Reitz         return NULL;
133421205c7cSMax Reitz     }
133521205c7cSMax Reitz 
133621205c7cSMax Reitz     if (bs->exact_filename[0] == '\0') {
133721205c7cSMax Reitz         error_setg(errp, "Cannot generate a base directory for this ssh node");
133821205c7cSMax Reitz         return NULL;
133921205c7cSMax Reitz     }
134021205c7cSMax Reitz 
134121205c7cSMax Reitz     return path_combine(bs->exact_filename, "");
134221205c7cSMax Reitz }
134321205c7cSMax Reitz 
13442654267cSMax Reitz static const char *const ssh_strong_runtime_opts[] = {
13452654267cSMax Reitz     "host",
13462654267cSMax Reitz     "port",
13472654267cSMax Reitz     "path",
13482654267cSMax Reitz     "user",
13492654267cSMax Reitz     "host_key_check",
13502654267cSMax Reitz     "server.",
13512654267cSMax Reitz 
13522654267cSMax Reitz     NULL
13532654267cSMax Reitz };
13542654267cSMax Reitz 
13550a12ec87SRichard W.M. Jones static BlockDriver bdrv_ssh = {
13560a12ec87SRichard W.M. Jones     .format_name                  = "ssh",
13570a12ec87SRichard W.M. Jones     .protocol_name                = "ssh",
13580a12ec87SRichard W.M. Jones     .instance_size                = sizeof(BDRVSSHState),
13590a12ec87SRichard W.M. Jones     .bdrv_parse_filename          = ssh_parse_filename,
13600a12ec87SRichard W.M. Jones     .bdrv_file_open               = ssh_file_open,
13614906da7eSKevin Wolf     .bdrv_co_create               = ssh_co_create,
1362efc75e2aSStefan Hajnoczi     .bdrv_co_create_opts          = ssh_co_create_opts,
13630a12ec87SRichard W.M. Jones     .bdrv_close                   = ssh_close,
13640b3f21e6SRichard W.M. Jones     .bdrv_has_zero_init           = ssh_has_zero_init,
13650a12ec87SRichard W.M. Jones     .bdrv_co_readv                = ssh_co_readv,
13660a12ec87SRichard W.M. Jones     .bdrv_co_writev               = ssh_co_writev,
1367c86422c5SEmanuele Giuseppe Esposito     .bdrv_co_getlength            = ssh_co_getlength,
1368061ca8a3SKevin Wolf     .bdrv_co_truncate             = ssh_co_truncate,
13699a2d462eSRichard W.M. Jones     .bdrv_co_flush_to_disk        = ssh_co_flush,
1370b8c1f901SMax Reitz     .bdrv_refresh_filename        = ssh_refresh_filename,
137121205c7cSMax Reitz     .bdrv_dirname                 = ssh_bdrv_dirname,
1372766181feSChunyan Liu     .create_opts                  = &ssh_create_opts,
13732654267cSMax Reitz     .strong_runtime_opts          = ssh_strong_runtime_opts,
13740a12ec87SRichard W.M. Jones };
13750a12ec87SRichard W.M. Jones 
bdrv_ssh_init(void)13760a12ec87SRichard W.M. Jones static void bdrv_ssh_init(void)
13770a12ec87SRichard W.M. Jones {
13780a12ec87SRichard W.M. Jones     int r;
13790a12ec87SRichard W.M. Jones 
1380b10d49d7SPino Toscano     r = ssh_init();
13810a12ec87SRichard W.M. Jones     if (r != 0) {
1382b10d49d7SPino Toscano         fprintf(stderr, "libssh initialization failed, %d\n", r);
13830a12ec87SRichard W.M. Jones         exit(EXIT_FAILURE);
13840a12ec87SRichard W.M. Jones     }
13850a12ec87SRichard W.M. Jones 
1386b10d49d7SPino Toscano #if TRACE_LIBSSH != 0
1387b10d49d7SPino Toscano     ssh_set_log_level(TRACE_LIBSSH);
1388b10d49d7SPino Toscano #endif
1389b10d49d7SPino Toscano 
13900a12ec87SRichard W.M. Jones     bdrv_register(&bdrv_ssh);
13910a12ec87SRichard W.M. Jones }
13920a12ec87SRichard W.M. Jones 
13930a12ec87SRichard W.M. Jones block_init(bdrv_ssh_init);
1394