xref: /openbmc/qemu/block/ssh.c (revision bd5629db935a6c17c86ffbb6a39aa85eed807346)
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"
409af23989SMarkus Armbruster #include "qapi/qapi-visit-sockets.h"
4116e4bdb1SKevin Wolf #include "qapi/qapi-visit-block-core.h"
42452fcdbcSMarkus Armbruster #include "qapi/qmp/qdict.h"
43d49b6836SMarkus Armbruster #include "qapi/qmp/qstring.h"
440da5b8efSAshijeet Acharya #include "qapi/qobject-input-visitor.h"
450da5b8efSAshijeet Acharya #include "qapi/qobject-output-visitor.h"
46023908a2SLaurent Vivier #include "trace.h"
470a12ec87SRichard W.M. Jones 
48023908a2SLaurent Vivier /*
49b10d49d7SPino Toscano  * TRACE_LIBSSH=<level> enables tracing in libssh itself.
50b10d49d7SPino Toscano  * The meaning of <level> is described here:
51b10d49d7SPino Toscano  * http://api.libssh.org/master/group__libssh__log.html
520a12ec87SRichard W.M. Jones  */
53b10d49d7SPino Toscano #define TRACE_LIBSSH  0 /* see: SSH_LOG_* */
540a12ec87SRichard W.M. Jones 
550a12ec87SRichard W.M. Jones typedef struct BDRVSSHState {
560a12ec87SRichard W.M. Jones     /* Coroutine. */
570a12ec87SRichard W.M. Jones     CoMutex lock;
580a12ec87SRichard W.M. Jones 
590a12ec87SRichard W.M. Jones     /* SSH connection. */
600a12ec87SRichard W.M. Jones     int sock;                         /* socket */
61b10d49d7SPino Toscano     ssh_session session;              /* ssh session */
62b10d49d7SPino Toscano     sftp_session sftp;                /* sftp session */
63b10d49d7SPino Toscano     sftp_file sftp_handle;            /* sftp remote file handle */
640a12ec87SRichard W.M. Jones 
65b10d49d7SPino Toscano     /*
66b10d49d7SPino Toscano      * File attributes at open.  We try to keep the .size field
670a12ec87SRichard W.M. Jones      * updated if it changes (eg by writing at the end of the file).
680a12ec87SRichard W.M. Jones      */
69b10d49d7SPino Toscano     sftp_attributes attrs;
709a2d462eSRichard W.M. Jones 
710da5b8efSAshijeet Acharya     InetSocketAddress *inet;
720da5b8efSAshijeet Acharya 
739a2d462eSRichard W.M. Jones     /* Used to warn if 'flush' is not supported. */
749a2d462eSRichard W.M. Jones     bool unsafe_flush_warning;
75b8c1f901SMax Reitz 
76b8c1f901SMax Reitz     /*
77b8c1f901SMax Reitz      * Store the user name for ssh_refresh_filename() because the
78b8c1f901SMax Reitz      * default depends on the system you are on -- therefore, when we
79b8c1f901SMax Reitz      * generate a filename, it should always contain the user name we
80b8c1f901SMax Reitz      * are actually using.
81b8c1f901SMax Reitz      */
82b8c1f901SMax Reitz     char *user;
830a12ec87SRichard W.M. Jones } BDRVSSHState;
840a12ec87SRichard W.M. Jones 
ssh_state_init(BDRVSSHState * s)850a12ec87SRichard W.M. Jones static void ssh_state_init(BDRVSSHState *s)
860a12ec87SRichard W.M. Jones {
870a12ec87SRichard W.M. Jones     memset(s, 0, sizeof *s);
880a12ec87SRichard W.M. Jones     s->sock = -1;
890a12ec87SRichard W.M. Jones     qemu_co_mutex_init(&s->lock);
900a12ec87SRichard W.M. Jones }
910a12ec87SRichard W.M. Jones 
ssh_state_free(BDRVSSHState * s)920a12ec87SRichard W.M. Jones static void ssh_state_free(BDRVSSHState *s)
930a12ec87SRichard W.M. Jones {
94b8c1f901SMax Reitz     g_free(s->user);
95b8c1f901SMax Reitz 
96b10d49d7SPino Toscano     if (s->attrs) {
97b10d49d7SPino Toscano         sftp_attributes_free(s->attrs);
98b10d49d7SPino Toscano     }
990a12ec87SRichard W.M. Jones     if (s->sftp_handle) {
100b10d49d7SPino Toscano         sftp_close(s->sftp_handle);
1010a12ec87SRichard W.M. Jones     }
1020a12ec87SRichard W.M. Jones     if (s->sftp) {
103b10d49d7SPino Toscano         sftp_free(s->sftp);
1040a12ec87SRichard W.M. Jones     }
1050a12ec87SRichard W.M. Jones     if (s->session) {
106b10d49d7SPino Toscano         ssh_disconnect(s->session);
107b10d49d7SPino Toscano         ssh_free(s->session); /* This frees s->sock */
1080a12ec87SRichard W.M. Jones     }
1090a12ec87SRichard W.M. Jones }
1100a12ec87SRichard W.M. Jones 
1119edc6313SMarc-André Lureau static void G_GNUC_PRINTF(3, 4)
session_error_setg(Error ** errp,BDRVSSHState * s,const char * fs,...)11201c2b265SMarkus Armbruster session_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
11301c2b265SMarkus Armbruster {
11401c2b265SMarkus Armbruster     va_list args;
11501c2b265SMarkus Armbruster     char *msg;
11601c2b265SMarkus Armbruster 
11701c2b265SMarkus Armbruster     va_start(args, fs);
11801c2b265SMarkus Armbruster     msg = g_strdup_vprintf(fs, args);
11901c2b265SMarkus Armbruster     va_end(args);
12001c2b265SMarkus Armbruster 
12101c2b265SMarkus Armbruster     if (s->session) {
122b10d49d7SPino Toscano         const char *ssh_err;
12301c2b265SMarkus Armbruster         int ssh_err_code;
12401c2b265SMarkus Armbruster 
125b10d49d7SPino Toscano         /* This is not an errno.  See <libssh/libssh.h>. */
126b10d49d7SPino Toscano         ssh_err = ssh_get_error(s->session);
127b10d49d7SPino Toscano         ssh_err_code = ssh_get_error_code(s->session);
128b10d49d7SPino Toscano         error_setg(errp, "%s: %s (libssh error code: %d)",
12901c2b265SMarkus Armbruster                    msg, ssh_err, ssh_err_code);
13001c2b265SMarkus Armbruster     } else {
13101c2b265SMarkus Armbruster         error_setg(errp, "%s", msg);
13201c2b265SMarkus Armbruster     }
13301c2b265SMarkus Armbruster     g_free(msg);
13401c2b265SMarkus Armbruster }
13501c2b265SMarkus Armbruster 
1369edc6313SMarc-André Lureau static void G_GNUC_PRINTF(3, 4)
sftp_error_setg(Error ** errp,BDRVSSHState * s,const char * fs,...)1375496fb1aSMarkus Armbruster sftp_error_setg(Error **errp, BDRVSSHState *s, const char *fs, ...)
1380a12ec87SRichard W.M. Jones {
1390a12ec87SRichard W.M. Jones     va_list args;
1405496fb1aSMarkus Armbruster     char *msg;
1410a12ec87SRichard W.M. Jones 
1420a12ec87SRichard W.M. Jones     va_start(args, fs);
1435496fb1aSMarkus Armbruster     msg = g_strdup_vprintf(fs, args);
1445496fb1aSMarkus Armbruster     va_end(args);
1450a12ec87SRichard W.M. Jones 
1465496fb1aSMarkus Armbruster     if (s->sftp) {
147b10d49d7SPino Toscano         const char *ssh_err;
1480a12ec87SRichard W.M. Jones         int ssh_err_code;
149b10d49d7SPino Toscano         int sftp_err_code;
1500a12ec87SRichard W.M. Jones 
151b10d49d7SPino Toscano         /* This is not an errno.  See <libssh/libssh.h>. */
152b10d49d7SPino Toscano         ssh_err = ssh_get_error(s->session);
153b10d49d7SPino Toscano         ssh_err_code = ssh_get_error_code(s->session);
154b10d49d7SPino Toscano         /* See <libssh/sftp.h>. */
155b10d49d7SPino Toscano         sftp_err_code = sftp_get_error(s->sftp);
1560a12ec87SRichard W.M. Jones 
1575496fb1aSMarkus Armbruster         error_setg(errp,
158b10d49d7SPino Toscano                    "%s: %s (libssh error code: %d, sftp error code: %d)",
1595496fb1aSMarkus Armbruster                    msg, ssh_err, ssh_err_code, sftp_err_code);
1605496fb1aSMarkus Armbruster     } else {
1615496fb1aSMarkus Armbruster         error_setg(errp, "%s", msg);
1625496fb1aSMarkus Armbruster     }
1635496fb1aSMarkus Armbruster     g_free(msg);
1640a12ec87SRichard W.M. Jones }
1650a12ec87SRichard W.M. Jones 
sftp_error_trace(BDRVSSHState * s,const char * op)1666b3048ceSMarkus Armbruster static void sftp_error_trace(BDRVSSHState *s, const char *op)
1670a12ec87SRichard W.M. Jones {
168b10d49d7SPino Toscano     const char *ssh_err;
1690a12ec87SRichard W.M. Jones     int ssh_err_code;
170b10d49d7SPino Toscano     int sftp_err_code;
1710a12ec87SRichard W.M. Jones 
172b10d49d7SPino Toscano     /* This is not an errno.  See <libssh/libssh.h>. */
173b10d49d7SPino Toscano     ssh_err = ssh_get_error(s->session);
174b10d49d7SPino Toscano     ssh_err_code = ssh_get_error_code(s->session);
175b10d49d7SPino Toscano     /* See <libssh/sftp.h>. */
176b10d49d7SPino Toscano     sftp_err_code = sftp_get_error(s->sftp);
1770a12ec87SRichard W.M. Jones 
1786b3048ceSMarkus Armbruster     trace_sftp_error(op, ssh_err, ssh_err_code, sftp_err_code);
1790a12ec87SRichard W.M. Jones }
1800a12ec87SRichard W.M. Jones 
parse_uri(const char * filename,QDict * options,Error ** errp)1810a12ec87SRichard W.M. Jones static int parse_uri(const char *filename, QDict *options, Error **errp)
1820a12ec87SRichard W.M. Jones {
183a22a97d8SThomas Huth     g_autoptr(GUri) uri = g_uri_parse(filename, G_URI_FLAGS_NONE, NULL);
184a22a97d8SThomas Huth     const char *uri_host, *uri_path, *uri_user, *uri_query;
1851059f1bbSAshijeet Acharya     char *port_str;
186a22a97d8SThomas Huth     int port;
187a22a97d8SThomas Huth     g_autoptr(GError) gerror = NULL;
188a22a97d8SThomas Huth     char *qp_name, *qp_value;
189a22a97d8SThomas Huth     GUriParamsIter qp;
1900a12ec87SRichard W.M. Jones 
1910a12ec87SRichard W.M. Jones     if (!uri) {
1920a12ec87SRichard W.M. Jones         return -EINVAL;
1930a12ec87SRichard W.M. Jones     }
1940a12ec87SRichard W.M. Jones 
195a22a97d8SThomas Huth     if (g_strcmp0(g_uri_get_scheme(uri), "ssh") != 0) {
1960a12ec87SRichard W.M. Jones         error_setg(errp, "URI scheme must be 'ssh'");
197a22a97d8SThomas Huth         return -EINVAL;
1980a12ec87SRichard W.M. Jones     }
1990a12ec87SRichard W.M. Jones 
200a22a97d8SThomas Huth     uri_host = g_uri_get_host(uri);
201a22a97d8SThomas Huth     if (!uri_host || g_str_equal(uri_host, "")) {
2020a12ec87SRichard W.M. Jones         error_setg(errp, "missing hostname in URI");
203a22a97d8SThomas Huth         return -EINVAL;
2040a12ec87SRichard W.M. Jones     }
2050a12ec87SRichard W.M. Jones 
206a22a97d8SThomas Huth     uri_path = g_uri_get_path(uri);
207a22a97d8SThomas Huth     if (!uri_path || g_str_equal(uri_path, "")) {
2080a12ec87SRichard W.M. Jones         error_setg(errp, "missing remote path in URI");
209a22a97d8SThomas Huth         return -EINVAL;
2100a12ec87SRichard W.M. Jones     }
2110a12ec87SRichard W.M. Jones 
212a22a97d8SThomas Huth     uri_user = g_uri_get_user(uri);
213a22a97d8SThomas Huth     if (uri_user && !g_str_equal(uri_user, "")) {
214a22a97d8SThomas Huth         qdict_put_str(options, "user", uri_user);
2150a12ec87SRichard W.M. Jones     }
2160a12ec87SRichard W.M. Jones 
217a22a97d8SThomas Huth     qdict_put_str(options, "server.host", uri_host);
2180a12ec87SRichard W.M. Jones 
219a22a97d8SThomas Huth     port = g_uri_get_port(uri);
220a22a97d8SThomas Huth     port_str = g_strdup_printf("%d", port > 0 ? port : 22);
22146f5ac20SEric Blake     qdict_put_str(options, "server.port", port_str);
2221059f1bbSAshijeet Acharya     g_free(port_str);
2230a12ec87SRichard W.M. Jones 
224a22a97d8SThomas Huth     qdict_put_str(options, "path", uri_path);
2250a12ec87SRichard W.M. Jones 
226a22a97d8SThomas Huth     uri_query = g_uri_get_query(uri);
227a22a97d8SThomas Huth     if (uri_query) {
228a22a97d8SThomas Huth         g_uri_params_iter_init(&qp, uri_query, -1, "&", G_URI_PARAMS_NONE);
229a22a97d8SThomas Huth         while (g_uri_params_iter_next(&qp, &qp_name, &qp_value, &gerror)) {
230a22a97d8SThomas Huth             if (!qp_name || !qp_value || gerror) {
231a22a97d8SThomas Huth                 warn_report("Failed to parse SSH URI parameters '%s'",
232a22a97d8SThomas Huth                             uri_query);
233a22a97d8SThomas Huth                 break;
234a22a97d8SThomas Huth             }
235a22a97d8SThomas Huth             /*
236a22a97d8SThomas Huth              * Pick out the query parameters that we understand, and ignore
237a22a97d8SThomas Huth              * (or rather warn about) the rest.
2380a12ec87SRichard W.M. Jones              */
239a22a97d8SThomas Huth             if (g_str_equal(qp_name, "host_key_check")) {
240a22a97d8SThomas Huth                 qdict_put_str(options, "host_key_check", qp_value);
241a22a97d8SThomas Huth             } else {
242a22a97d8SThomas Huth                 warn_report("Unsupported parameter '%s' in URI", qp_name);
243a22a97d8SThomas Huth             }
2440a12ec87SRichard W.M. Jones         }
2450a12ec87SRichard W.M. Jones     }
2460a12ec87SRichard W.M. Jones 
2470a12ec87SRichard W.M. Jones     return 0;
2480a12ec87SRichard W.M. Jones }
2490a12ec87SRichard W.M. Jones 
ssh_has_filename_options_conflict(QDict * options,Error ** errp)25089dbe180SAshijeet Acharya static bool ssh_has_filename_options_conflict(QDict *options, Error **errp)
25189dbe180SAshijeet Acharya {
25289dbe180SAshijeet Acharya     const QDictEntry *qe;
25389dbe180SAshijeet Acharya 
25489dbe180SAshijeet Acharya     for (qe = qdict_first(options); qe; qe = qdict_next(options, qe)) {
25589dbe180SAshijeet Acharya         if (!strcmp(qe->key, "host") ||
25689dbe180SAshijeet Acharya             !strcmp(qe->key, "port") ||
25789dbe180SAshijeet Acharya             !strcmp(qe->key, "path") ||
25889dbe180SAshijeet Acharya             !strcmp(qe->key, "user") ||
2590da5b8efSAshijeet Acharya             !strcmp(qe->key, "host_key_check") ||
2600da5b8efSAshijeet Acharya             strstart(qe->key, "server.", NULL))
26189dbe180SAshijeet Acharya         {
26289dbe180SAshijeet Acharya             error_setg(errp, "Option '%s' cannot be used with a file name",
26389dbe180SAshijeet Acharya                        qe->key);
26489dbe180SAshijeet Acharya             return true;
26589dbe180SAshijeet Acharya         }
26689dbe180SAshijeet Acharya     }
26789dbe180SAshijeet Acharya 
26889dbe180SAshijeet Acharya     return false;
26989dbe180SAshijeet Acharya }
27089dbe180SAshijeet Acharya 
ssh_parse_filename(const char * filename,QDict * options,Error ** errp)2710a12ec87SRichard W.M. Jones static void ssh_parse_filename(const char *filename, QDict *options,
2720a12ec87SRichard W.M. Jones                                Error **errp)
2730a12ec87SRichard W.M. Jones {
27489dbe180SAshijeet Acharya     if (ssh_has_filename_options_conflict(options, errp)) {
2750a12ec87SRichard W.M. Jones         return;
2760a12ec87SRichard W.M. Jones     }
2770a12ec87SRichard W.M. Jones 
2780a12ec87SRichard W.M. Jones     parse_uri(filename, options, errp);
2790a12ec87SRichard W.M. Jones }
2800a12ec87SRichard W.M. Jones 
check_host_key_knownhosts(BDRVSSHState * s,Error ** errp)281b10d49d7SPino Toscano static int check_host_key_knownhosts(BDRVSSHState *s, Error **errp)
2820a12ec87SRichard W.M. Jones {
283b10d49d7SPino Toscano     int ret;
284b10d49d7SPino Toscano     enum ssh_known_hosts_e state;
285b10d49d7SPino Toscano     int r;
286b10d49d7SPino Toscano     ssh_key pubkey;
287b10d49d7SPino Toscano     enum ssh_keytypes_e pubkey_type;
288b10d49d7SPino Toscano     unsigned char *server_hash = NULL;
289b10d49d7SPino Toscano     size_t server_hash_len;
290b10d49d7SPino Toscano     char *fingerprint = NULL;
2910a12ec87SRichard W.M. Jones 
292b10d49d7SPino Toscano     state = ssh_session_is_known_server(s->session);
293b10d49d7SPino Toscano     trace_ssh_server_status(state);
2940a12ec87SRichard W.M. Jones 
295b10d49d7SPino Toscano     switch (state) {
296b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_OK:
2970a12ec87SRichard W.M. Jones         /* OK */
298b10d49d7SPino Toscano         trace_ssh_check_host_key_knownhosts();
2990a12ec87SRichard W.M. Jones         break;
300b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_CHANGED:
3010a12ec87SRichard W.M. Jones         ret = -EINVAL;
302b10d49d7SPino Toscano         r = ssh_get_server_publickey(s->session, &pubkey);
303b10d49d7SPino Toscano         if (r == 0) {
304b10d49d7SPino Toscano             r = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256,
305b10d49d7SPino Toscano                                        &server_hash, &server_hash_len);
306b10d49d7SPino Toscano             pubkey_type = ssh_key_type(pubkey);
307b10d49d7SPino Toscano             ssh_key_free(pubkey);
308b10d49d7SPino Toscano         }
309b10d49d7SPino Toscano         if (r == 0) {
310b10d49d7SPino Toscano             fingerprint = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256,
311b10d49d7SPino Toscano                                                    server_hash,
312b10d49d7SPino Toscano                                                    server_hash_len);
313b10d49d7SPino Toscano             ssh_clean_pubkey_hash(&server_hash);
314b10d49d7SPino Toscano         }
315b10d49d7SPino Toscano         if (fingerprint) {
316b10d49d7SPino Toscano             error_setg(errp,
317b10d49d7SPino Toscano                        "host key (%s key with fingerprint %s) does not match "
318b10d49d7SPino Toscano                        "the one in known_hosts; this may be a possible attack",
319b10d49d7SPino Toscano                        ssh_key_type_to_char(pubkey_type), fingerprint);
320b10d49d7SPino Toscano             ssh_string_free_char(fingerprint);
321b10d49d7SPino Toscano         } else  {
322b10d49d7SPino Toscano             error_setg(errp,
323b10d49d7SPino Toscano                        "host key does not match the one in known_hosts; this "
324b10d49d7SPino Toscano                        "may be a possible attack");
325b10d49d7SPino Toscano         }
3260a12ec87SRichard W.M. Jones         goto out;
327b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_OTHER:
3280a12ec87SRichard W.M. Jones         ret = -EINVAL;
329b10d49d7SPino Toscano         error_setg(errp,
330b10d49d7SPino Toscano                    "host key for this server not found, another type exists");
3310a12ec87SRichard W.M. Jones         goto out;
332b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_UNKNOWN:
3330a12ec87SRichard W.M. Jones         ret = -EINVAL;
334b10d49d7SPino Toscano         error_setg(errp, "no host key was found in known_hosts");
335b10d49d7SPino Toscano         goto out;
336b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_NOT_FOUND:
337b10d49d7SPino Toscano         ret = -ENOENT;
338b10d49d7SPino Toscano         error_setg(errp, "known_hosts file not found");
339b10d49d7SPino Toscano         goto out;
340b10d49d7SPino Toscano     case SSH_KNOWN_HOSTS_ERROR:
341b10d49d7SPino Toscano         ret = -EINVAL;
342b10d49d7SPino Toscano         error_setg(errp, "error while checking the host");
3430a12ec87SRichard W.M. Jones         goto out;
3440a12ec87SRichard W.M. Jones     default:
3450a12ec87SRichard W.M. Jones         ret = -EINVAL;
346b10d49d7SPino Toscano         error_setg(errp, "error while checking for known server (%d)", state);
3470a12ec87SRichard W.M. Jones         goto out;
3480a12ec87SRichard W.M. Jones     }
3490a12ec87SRichard W.M. Jones 
3500a12ec87SRichard W.M. Jones     /* known_hosts checking successful. */
3510a12ec87SRichard W.M. Jones     ret = 0;
3520a12ec87SRichard W.M. Jones 
3530a12ec87SRichard W.M. Jones  out:
3540a12ec87SRichard W.M. Jones     return ret;
3550a12ec87SRichard W.M. Jones }
3560a12ec87SRichard W.M. Jones 
hex2decimal(char ch)3570a12ec87SRichard W.M. Jones static unsigned hex2decimal(char ch)
3580a12ec87SRichard W.M. Jones {
3590a12ec87SRichard W.M. Jones     if (ch >= '0' && ch <= '9') {
3600a12ec87SRichard W.M. Jones         return (ch - '0');
3610a12ec87SRichard W.M. Jones     } else if (ch >= 'a' && ch <= 'f') {
3620a12ec87SRichard W.M. Jones         return 10 + (ch - 'a');
3630a12ec87SRichard W.M. Jones     } else if (ch >= 'A' && ch <= 'F') {
3640a12ec87SRichard W.M. Jones         return 10 + (ch - 'A');
3650a12ec87SRichard W.M. Jones     }
3660a12ec87SRichard W.M. Jones 
367*19c1e441SPeter Maydell     return UINT_MAX;
3680a12ec87SRichard W.M. Jones }
3690a12ec87SRichard W.M. Jones 
3700a12ec87SRichard W.M. Jones /* Compare the binary fingerprint (hash of host key) with the
3710a12ec87SRichard W.M. Jones  * host_key_check parameter.
3720a12ec87SRichard W.M. Jones  */
compare_fingerprint(const unsigned char * fingerprint,size_t len,const char * host_key_check)3730a12ec87SRichard W.M. Jones static int compare_fingerprint(const unsigned char *fingerprint, size_t len,
3740a12ec87SRichard W.M. Jones                                const char *host_key_check)
3750a12ec87SRichard W.M. Jones {
3760a12ec87SRichard W.M. Jones     unsigned c;
3770a12ec87SRichard W.M. Jones 
3780a12ec87SRichard W.M. Jones     while (len > 0) {
379*19c1e441SPeter Maydell         unsigned c0, c1;
3800a12ec87SRichard W.M. Jones         while (*host_key_check == ':')
3810a12ec87SRichard W.M. Jones             host_key_check++;
382*19c1e441SPeter Maydell         c0 = hex2decimal(host_key_check[0]);
383*19c1e441SPeter Maydell         c1 = hex2decimal(host_key_check[1]);
384*19c1e441SPeter Maydell         if (c0 > 0xf || c1 > 0xf) {
3850a12ec87SRichard W.M. Jones             return 1;
386*19c1e441SPeter Maydell         }
387*19c1e441SPeter Maydell         c = c0 * 16 + c1;
3880a12ec87SRichard W.M. Jones         if (c - *fingerprint != 0)
3890a12ec87SRichard W.M. Jones             return c - *fingerprint;
3900a12ec87SRichard W.M. Jones         fingerprint++;
3910a12ec87SRichard W.M. Jones         len--;
3920a12ec87SRichard W.M. Jones         host_key_check += 2;
3930a12ec87SRichard W.M. Jones     }
3940a12ec87SRichard W.M. Jones     return *host_key_check - '\0';
3950a12ec87SRichard W.M. Jones }
3960a12ec87SRichard W.M. Jones 
format_fingerprint(const unsigned char * fingerprint,size_t len)397e3296cc7SDaniel P. Berrangé static char *format_fingerprint(const unsigned char *fingerprint, size_t len)
398e3296cc7SDaniel P. Berrangé {
399e3296cc7SDaniel P. Berrangé     static const char *hex = "0123456789abcdef";
400e3296cc7SDaniel P. Berrangé     char *ret = g_new0(char, (len * 2) + 1);
401e3296cc7SDaniel P. Berrangé     for (size_t i = 0; i < len; i++) {
402e3296cc7SDaniel P. Berrangé         ret[i * 2] = hex[((fingerprint[i] >> 4) & 0xf)];
403e3296cc7SDaniel P. Berrangé         ret[(i * 2) + 1] = hex[(fingerprint[i] & 0xf)];
404e3296cc7SDaniel P. Berrangé     }
405e3296cc7SDaniel P. Berrangé     ret[len * 2] = '\0';
406e3296cc7SDaniel P. Berrangé     return ret;
407e3296cc7SDaniel P. Berrangé }
408e3296cc7SDaniel P. Berrangé 
4090a12ec87SRichard 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)4100a12ec87SRichard W.M. Jones check_host_key_hash(BDRVSSHState *s, const char *hash,
411e3296cc7SDaniel P. Berrangé                     enum ssh_publickey_hash_type type, const char *typestr,
412e3296cc7SDaniel P. Berrangé                     Error **errp)
4130a12ec87SRichard W.M. Jones {
414b10d49d7SPino Toscano     int r;
415b10d49d7SPino Toscano     ssh_key pubkey;
416b10d49d7SPino Toscano     unsigned char *server_hash;
417b10d49d7SPino Toscano     size_t server_hash_len;
418e3296cc7SDaniel P. Berrangé     const char *keytype;
4190a12ec87SRichard W.M. Jones 
420b10d49d7SPino Toscano     r = ssh_get_server_publickey(s->session, &pubkey);
421b10d49d7SPino Toscano     if (r != SSH_OK) {
42201c2b265SMarkus Armbruster         session_error_setg(errp, s, "failed to read remote host key");
4230a12ec87SRichard W.M. Jones         return -EINVAL;
4240a12ec87SRichard W.M. Jones     }
4250a12ec87SRichard W.M. Jones 
426e3296cc7SDaniel P. Berrangé     keytype = ssh_key_type_to_char(ssh_key_type(pubkey));
427e3296cc7SDaniel P. Berrangé 
428b10d49d7SPino Toscano     r = ssh_get_publickey_hash(pubkey, type, &server_hash, &server_hash_len);
429b10d49d7SPino Toscano     ssh_key_free(pubkey);
430b10d49d7SPino Toscano     if (r != 0) {
431b10d49d7SPino Toscano         session_error_setg(errp, s,
432b10d49d7SPino Toscano                            "failed reading the hash of the server SSH key");
433b10d49d7SPino Toscano         return -EINVAL;
434b10d49d7SPino Toscano     }
435b10d49d7SPino Toscano 
436b10d49d7SPino Toscano     r = compare_fingerprint(server_hash, server_hash_len, hash);
437b10d49d7SPino Toscano     if (r != 0) {
438e3296cc7SDaniel P. Berrangé         g_autofree char *server_fp = format_fingerprint(server_hash,
439e3296cc7SDaniel P. Berrangé                                                         server_hash_len);
440e3296cc7SDaniel P. Berrangé         error_setg(errp, "remote host %s key fingerprint '%s:%s' "
441e3296cc7SDaniel P. Berrangé                    "does not match host_key_check '%s:%s'",
442e3296cc7SDaniel P. Berrangé                    keytype, typestr, server_fp, typestr, hash);
443e3296cc7SDaniel P. Berrangé         ssh_clean_pubkey_hash(&server_hash);
4440a12ec87SRichard W.M. Jones         return -EPERM;
4450a12ec87SRichard W.M. Jones     }
446e3296cc7SDaniel P. Berrangé     ssh_clean_pubkey_hash(&server_hash);
4470a12ec87SRichard W.M. Jones 
4480a12ec87SRichard W.M. Jones     return 0;
4490a12ec87SRichard W.M. Jones }
4500a12ec87SRichard W.M. Jones 
check_host_key(BDRVSSHState * s,SshHostKeyCheck * hkc,Error ** errp)451b10d49d7SPino Toscano static int check_host_key(BDRVSSHState *s, SshHostKeyCheck *hkc, Error **errp)
4520a12ec87SRichard W.M. Jones {
453ec2f5418SKevin Wolf     SshHostKeyCheckMode mode;
454ec2f5418SKevin Wolf 
455ec2f5418SKevin Wolf     if (hkc) {
456ec2f5418SKevin Wolf         mode = hkc->mode;
457ec2f5418SKevin Wolf     } else {
458ec2f5418SKevin Wolf         mode = SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS;
459ec2f5418SKevin Wolf     }
460ec2f5418SKevin Wolf 
461ec2f5418SKevin Wolf     switch (mode) {
462ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_NONE:
4630a12ec87SRichard W.M. Jones         return 0;
464ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_HASH:
465ec2f5418SKevin Wolf         if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) {
466ec2f5418SKevin Wolf             return check_host_key_hash(s, hkc->u.hash.hash,
467e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_MD5, "md5",
468e3296cc7SDaniel P. Berrangé                                        errp);
469ec2f5418SKevin Wolf         } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) {
470ec2f5418SKevin Wolf             return check_host_key_hash(s, hkc->u.hash.hash,
471e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_SHA1, "sha1",
472e3296cc7SDaniel P. Berrangé                                        errp);
473bf783261SDaniel P. Berrangé         } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA256) {
474bf783261SDaniel P. Berrangé             return check_host_key_hash(s, hkc->u.hash.hash,
475e3296cc7SDaniel P. Berrangé                                        SSH_PUBLICKEY_HASH_SHA256, "sha256",
476e3296cc7SDaniel P. Berrangé                                        errp);
4770a12ec87SRichard W.M. Jones         }
478ec2f5418SKevin Wolf         g_assert_not_reached();
479ec2f5418SKevin Wolf     case SSH_HOST_KEY_CHECK_MODE_KNOWN_HOSTS:
480b10d49d7SPino Toscano         return check_host_key_knownhosts(s, errp);
481ec2f5418SKevin Wolf     default:
482ec2f5418SKevin Wolf         g_assert_not_reached();
4830a12ec87SRichard W.M. Jones     }
4840a12ec87SRichard W.M. Jones 
4850a12ec87SRichard W.M. Jones     return -EINVAL;
4860a12ec87SRichard W.M. Jones }
4870a12ec87SRichard W.M. Jones 
authenticate(BDRVSSHState * s,Error ** errp)488b10d49d7SPino Toscano static int authenticate(BDRVSSHState *s, Error **errp)
4890a12ec87SRichard W.M. Jones {
4900a12ec87SRichard W.M. Jones     int r, ret;
491b10d49d7SPino Toscano     int method;
4920a12ec87SRichard W.M. Jones 
493b10d49d7SPino Toscano     /* Try to authenticate with the "none" method. */
494b10d49d7SPino Toscano     r = ssh_userauth_none(s->session, NULL);
495b10d49d7SPino Toscano     if (r == SSH_AUTH_ERROR) {
4960a12ec87SRichard W.M. Jones         ret = -EPERM;
497b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to authenticate using none "
498b10d49d7SPino Toscano                                     "authentication");
4990a12ec87SRichard W.M. Jones         goto out;
500b10d49d7SPino Toscano     } else if (r == SSH_AUTH_SUCCESS) {
5010a12ec87SRichard W.M. Jones         /* Authenticated! */
5020a12ec87SRichard W.M. Jones         ret = 0;
5030a12ec87SRichard W.M. Jones         goto out;
5040a12ec87SRichard W.M. Jones     }
505b10d49d7SPino Toscano 
506b10d49d7SPino Toscano     method = ssh_userauth_list(s->session, NULL);
507b10d49d7SPino Toscano     trace_ssh_auth_methods(method);
508b10d49d7SPino Toscano 
509b10d49d7SPino Toscano     /*
510b10d49d7SPino Toscano      * Try to authenticate with publickey, using the ssh-agent
511b10d49d7SPino Toscano      * if available.
512b10d49d7SPino Toscano      */
513b10d49d7SPino Toscano     if (method & SSH_AUTH_METHOD_PUBLICKEY) {
514b10d49d7SPino Toscano         r = ssh_userauth_publickey_auto(s->session, NULL, NULL);
515b10d49d7SPino Toscano         if (r == SSH_AUTH_ERROR) {
516b10d49d7SPino Toscano             ret = -EINVAL;
517b10d49d7SPino Toscano             session_error_setg(errp, s, "failed to authenticate using "
518b10d49d7SPino Toscano                                         "publickey authentication");
519b10d49d7SPino Toscano             goto out;
520b10d49d7SPino Toscano         } else if (r == SSH_AUTH_SUCCESS) {
521b10d49d7SPino Toscano             /* Authenticated! */
522b10d49d7SPino Toscano             ret = 0;
523b10d49d7SPino Toscano             goto out;
524b10d49d7SPino Toscano         }
5250a12ec87SRichard W.M. Jones     }
5260a12ec87SRichard W.M. Jones 
5270a12ec87SRichard W.M. Jones     ret = -EPERM;
5284618e658SMarkus Armbruster     error_setg(errp, "failed to authenticate using publickey authentication "
5290a12ec87SRichard W.M. Jones                "and the identities held by your ssh-agent");
5300a12ec87SRichard W.M. Jones 
5310a12ec87SRichard W.M. Jones  out:
5320a12ec87SRichard W.M. Jones     return ret;
5330a12ec87SRichard W.M. Jones }
5340a12ec87SRichard W.M. Jones 
5358a6a8089SMax Reitz static QemuOptsList ssh_runtime_opts = {
5368a6a8089SMax Reitz     .name = "ssh",
5378a6a8089SMax Reitz     .head = QTAILQ_HEAD_INITIALIZER(ssh_runtime_opts.head),
5388a6a8089SMax Reitz     .desc = {
5398a6a8089SMax Reitz         {
5408a6a8089SMax Reitz             .name = "host",
5418a6a8089SMax Reitz             .type = QEMU_OPT_STRING,
5428a6a8089SMax Reitz             .help = "Host to connect to",
5438a6a8089SMax Reitz         },
5448a6a8089SMax Reitz         {
5458a6a8089SMax Reitz             .name = "port",
5468a6a8089SMax Reitz             .type = QEMU_OPT_NUMBER,
5478a6a8089SMax Reitz             .help = "Port to connect to",
5488a6a8089SMax Reitz         },
549ec2f5418SKevin Wolf         {
550ec2f5418SKevin Wolf             .name = "host_key_check",
551ec2f5418SKevin Wolf             .type = QEMU_OPT_STRING,
552ec2f5418SKevin Wolf             .help = "Defines how and what to check the host key against",
553ec2f5418SKevin Wolf         },
554fbd5c4c0SMurilo Opsfelder Araujo         { /* end of list */ }
5558a6a8089SMax Reitz     },
5568a6a8089SMax Reitz };
5578a6a8089SMax Reitz 
ssh_process_legacy_options(QDict * output_opts,QemuOpts * legacy_opts,Error ** errp)558ec2f5418SKevin Wolf static bool ssh_process_legacy_options(QDict *output_opts,
5590da5b8efSAshijeet Acharya                                        QemuOpts *legacy_opts,
5600da5b8efSAshijeet Acharya                                        Error **errp)
5610da5b8efSAshijeet Acharya {
5620da5b8efSAshijeet Acharya     const char *host = qemu_opt_get(legacy_opts, "host");
5630da5b8efSAshijeet Acharya     const char *port = qemu_opt_get(legacy_opts, "port");
564ec2f5418SKevin Wolf     const char *host_key_check = qemu_opt_get(legacy_opts, "host_key_check");
5650da5b8efSAshijeet Acharya 
5660da5b8efSAshijeet Acharya     if (!host && port) {
5670da5b8efSAshijeet Acharya         error_setg(errp, "port may not be used without host");
5680da5b8efSAshijeet Acharya         return false;
5690da5b8efSAshijeet Acharya     }
5700da5b8efSAshijeet Acharya 
5710da5b8efSAshijeet Acharya     if (host) {
57246f5ac20SEric Blake         qdict_put_str(output_opts, "server.host", host);
57346f5ac20SEric Blake         qdict_put_str(output_opts, "server.port", port ?: stringify(22));
5740da5b8efSAshijeet Acharya     }
5750da5b8efSAshijeet Acharya 
576ec2f5418SKevin Wolf     if (host_key_check) {
577ec2f5418SKevin Wolf         if (strcmp(host_key_check, "no") == 0) {
578ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "none");
579ec2f5418SKevin Wolf         } else if (strncmp(host_key_check, "md5:", 4) == 0) {
580ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "hash");
581ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.type", "md5");
582ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.hash",
583ec2f5418SKevin Wolf                           &host_key_check[4]);
584ec2f5418SKevin Wolf         } else if (strncmp(host_key_check, "sha1:", 5) == 0) {
585ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "hash");
586ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.type", "sha1");
587ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.hash",
588ec2f5418SKevin Wolf                           &host_key_check[5]);
589ea0f60e6SDaniel P. Berrangé         } else if (strncmp(host_key_check, "sha256:", 7) == 0) {
590ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.mode", "hash");
591ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.type", "sha256");
592ea0f60e6SDaniel P. Berrangé             qdict_put_str(output_opts, "host-key-check.hash",
593ea0f60e6SDaniel P. Berrangé                           &host_key_check[7]);
594ec2f5418SKevin Wolf         } else if (strcmp(host_key_check, "yes") == 0) {
595ec2f5418SKevin Wolf             qdict_put_str(output_opts, "host-key-check.mode", "known_hosts");
596ec2f5418SKevin Wolf         } else {
597ec2f5418SKevin Wolf             error_setg(errp, "unknown host_key_check setting (%s)",
598ec2f5418SKevin Wolf                        host_key_check);
599ec2f5418SKevin Wolf             return false;
600ec2f5418SKevin Wolf         }
601ec2f5418SKevin Wolf     }
602ec2f5418SKevin Wolf 
6030da5b8efSAshijeet Acharya     return true;
6040da5b8efSAshijeet Acharya }
6050da5b8efSAshijeet Acharya 
ssh_parse_options(QDict * options,Error ** errp)60616e4bdb1SKevin Wolf static BlockdevOptionsSsh *ssh_parse_options(QDict *options, Error **errp)
6070da5b8efSAshijeet Acharya {
60816e4bdb1SKevin Wolf     BlockdevOptionsSsh *result = NULL;
60916e4bdb1SKevin Wolf     QemuOpts *opts = NULL;
61016e4bdb1SKevin Wolf     const QDictEntry *e;
61116e4bdb1SKevin Wolf     Visitor *v;
6120da5b8efSAshijeet Acharya 
61316e4bdb1SKevin Wolf     /* Translate legacy options */
61416e4bdb1SKevin Wolf     opts = qemu_opts_create(&ssh_runtime_opts, NULL, 0, &error_abort);
615af175e85SMarkus Armbruster     if (!qemu_opts_absorb_qdict(opts, options, errp)) {
61616e4bdb1SKevin Wolf         goto fail;
6170da5b8efSAshijeet Acharya     }
6180da5b8efSAshijeet Acharya 
619ec2f5418SKevin Wolf     if (!ssh_process_legacy_options(options, opts, errp)) {
62016e4bdb1SKevin Wolf         goto fail;
62116e4bdb1SKevin Wolf     }
62216e4bdb1SKevin Wolf 
62316e4bdb1SKevin Wolf     /* Create the QAPI object */
624af91062eSMarkus Armbruster     v = qobject_input_visitor_new_flat_confused(options, errp);
625af91062eSMarkus Armbruster     if (!v) {
62616e4bdb1SKevin Wolf         goto fail;
6270da5b8efSAshijeet Acharya     }
6280da5b8efSAshijeet Acharya 
629b11a093cSMarkus Armbruster     visit_type_BlockdevOptionsSsh(v, NULL, &result, errp);
63016e4bdb1SKevin Wolf     visit_free(v);
631b11a093cSMarkus Armbruster     if (!result) {
63216e4bdb1SKevin Wolf         goto fail;
6330da5b8efSAshijeet Acharya     }
6340da5b8efSAshijeet Acharya 
63516e4bdb1SKevin Wolf     /* Remove the processed options from the QDict (the visitor processes
63616e4bdb1SKevin Wolf      * _all_ options in the QDict) */
63716e4bdb1SKevin Wolf     while ((e = qdict_first(options))) {
63816e4bdb1SKevin Wolf         qdict_del(options, e->key);
63916e4bdb1SKevin Wolf     }
64016e4bdb1SKevin Wolf 
64116e4bdb1SKevin Wolf fail:
64216e4bdb1SKevin Wolf     qemu_opts_del(opts);
64316e4bdb1SKevin Wolf     return result;
6440da5b8efSAshijeet Acharya }
6450da5b8efSAshijeet Acharya 
connect_to_ssh(BDRVSSHState * s,BlockdevOptionsSsh * opts,int ssh_flags,int creat_mode,Error ** errp)646375f0b92SKevin Wolf static int connect_to_ssh(BDRVSSHState *s, BlockdevOptionsSsh *opts,
6475f0c39e5SMarkus Armbruster                           int ssh_flags, int creat_mode, Error **errp)
6480a12ec87SRichard W.M. Jones {
6490a12ec87SRichard W.M. Jones     int r, ret;
650b10d49d7SPino Toscano     unsigned int port = 0;
651b10d49d7SPino Toscano     int new_sock = -1;
6520a12ec87SRichard W.M. Jones 
65354fde4ffSMarkus Armbruster     if (opts->user) {
654b8c1f901SMax Reitz         s->user = g_strdup(opts->user);
65516e4bdb1SKevin Wolf     } else {
656b8c1f901SMax Reitz         s->user = g_strdup(g_get_user_name());
657b8c1f901SMax Reitz         if (!s->user) {
6585f0c39e5SMarkus Armbruster             error_setg_errno(errp, errno, "Can't get user name");
6590a12ec87SRichard W.M. Jones             ret = -errno;
6600a12ec87SRichard W.M. Jones             goto err;
6610a12ec87SRichard W.M. Jones         }
6620a12ec87SRichard W.M. Jones     }
6630a12ec87SRichard W.M. Jones 
6640da5b8efSAshijeet Acharya     /* Pop the config into our state object, Exit if invalid */
66516e4bdb1SKevin Wolf     s->inet = opts->server;
66616e4bdb1SKevin Wolf     opts->server = NULL;
6670da5b8efSAshijeet Acharya 
668b10d49d7SPino Toscano     if (qemu_strtoui(s->inet->port, NULL, 10, &port) < 0) {
6690da5b8efSAshijeet Acharya         error_setg(errp, "Use only numeric port value");
6700da5b8efSAshijeet Acharya         ret = -EINVAL;
6710da5b8efSAshijeet Acharya         goto err;
6720da5b8efSAshijeet Acharya     }
6739a2d462eSRichard W.M. Jones 
6740a12ec87SRichard W.M. Jones     /* Open the socket and connect. */
675b10d49d7SPino Toscano     new_sock = inet_connect_saddr(s->inet, errp);
676b10d49d7SPino Toscano     if (new_sock < 0) {
677325e3904SRichard W.M. Jones         ret = -EIO;
6780a12ec87SRichard W.M. Jones         goto err;
6790a12ec87SRichard W.M. Jones     }
6800a12ec87SRichard W.M. Jones 
681b10d49d7SPino Toscano     /*
682b10d49d7SPino Toscano      * Try to disable the Nagle algorithm on TCP sockets to reduce latency,
683b10d49d7SPino Toscano      * but do not fail if it cannot be disabled.
684b10d49d7SPino Toscano      */
685b10d49d7SPino Toscano     r = socket_set_nodelay(new_sock);
686b10d49d7SPino Toscano     if (r < 0) {
687b10d49d7SPino Toscano         warn_report("can't set TCP_NODELAY for the ssh server %s: %s",
688b10d49d7SPino Toscano                     s->inet->host, strerror(errno));
689b10d49d7SPino Toscano     }
690b10d49d7SPino Toscano 
6910a12ec87SRichard W.M. Jones     /* Create SSH session. */
692b10d49d7SPino Toscano     s->session = ssh_new();
6930a12ec87SRichard W.M. Jones     if (!s->session) {
6940a12ec87SRichard W.M. Jones         ret = -EINVAL;
695b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to initialize libssh session");
6960a12ec87SRichard W.M. Jones         goto err;
6970a12ec87SRichard W.M. Jones     }
6980a12ec87SRichard W.M. Jones 
699b10d49d7SPino Toscano     /*
700b10d49d7SPino Toscano      * Make sure we are in blocking mode during the connection and
701b10d49d7SPino Toscano      * authentication phases.
702b10d49d7SPino Toscano      */
703b10d49d7SPino Toscano     ssh_set_blocking(s->session, 1);
7040a12ec87SRichard W.M. Jones 
705b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_USER, s->user);
706b10d49d7SPino Toscano     if (r < 0) {
707b10d49d7SPino Toscano         ret = -EINVAL;
708b10d49d7SPino Toscano         session_error_setg(errp, s,
709b10d49d7SPino Toscano                            "failed to set the user in the libssh session");
710b10d49d7SPino Toscano         goto err;
711b10d49d7SPino Toscano     }
712b10d49d7SPino Toscano 
713b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_HOST, s->inet->host);
714b10d49d7SPino Toscano     if (r < 0) {
715b10d49d7SPino Toscano         ret = -EINVAL;
716b10d49d7SPino Toscano         session_error_setg(errp, s,
717b10d49d7SPino Toscano                            "failed to set the host in the libssh session");
718b10d49d7SPino Toscano         goto err;
719b10d49d7SPino Toscano     }
720b10d49d7SPino Toscano 
721b10d49d7SPino Toscano     if (port > 0) {
722b10d49d7SPino Toscano         r = ssh_options_set(s->session, SSH_OPTIONS_PORT, &port);
723b10d49d7SPino Toscano         if (r < 0) {
724b10d49d7SPino Toscano             ret = -EINVAL;
725b10d49d7SPino Toscano             session_error_setg(errp, s,
726b10d49d7SPino Toscano                                "failed to set the port in the libssh session");
727b10d49d7SPino Toscano             goto err;
728b10d49d7SPino Toscano         }
729b10d49d7SPino Toscano     }
730b10d49d7SPino Toscano 
731b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_COMPRESSION, "none");
732b10d49d7SPino Toscano     if (r < 0) {
733b10d49d7SPino Toscano         ret = -EINVAL;
734b10d49d7SPino Toscano         session_error_setg(errp, s,
735b10d49d7SPino Toscano                            "failed to disable the compression in the libssh "
736b10d49d7SPino Toscano                            "session");
737b10d49d7SPino Toscano         goto err;
738b10d49d7SPino Toscano     }
739b10d49d7SPino Toscano 
740b10d49d7SPino Toscano     /* Read ~/.ssh/config. */
741b10d49d7SPino Toscano     r = ssh_options_parse_config(s->session, NULL);
742b10d49d7SPino Toscano     if (r < 0) {
743b10d49d7SPino Toscano         ret = -EINVAL;
744b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to parse ~/.ssh/config");
745b10d49d7SPino Toscano         goto err;
746b10d49d7SPino Toscano     }
747b10d49d7SPino Toscano 
748b10d49d7SPino Toscano     r = ssh_options_set(s->session, SSH_OPTIONS_FD, &new_sock);
749b10d49d7SPino Toscano     if (r < 0) {
750b10d49d7SPino Toscano         ret = -EINVAL;
751b10d49d7SPino Toscano         session_error_setg(errp, s,
752b10d49d7SPino Toscano                            "failed to set the socket in the libssh session");
753b10d49d7SPino Toscano         goto err;
754b10d49d7SPino Toscano     }
755b10d49d7SPino Toscano     /* libssh took ownership of the socket. */
756b10d49d7SPino Toscano     s->sock = new_sock;
757b10d49d7SPino Toscano     new_sock = -1;
758b10d49d7SPino Toscano 
759b10d49d7SPino Toscano     /* Connect. */
760b10d49d7SPino Toscano     r = ssh_connect(s->session);
761b10d49d7SPino Toscano     if (r != SSH_OK) {
7620a12ec87SRichard W.M. Jones         ret = -EINVAL;
7635f0c39e5SMarkus Armbruster         session_error_setg(errp, s, "failed to establish SSH session");
7640a12ec87SRichard W.M. Jones         goto err;
7650a12ec87SRichard W.M. Jones     }
7660a12ec87SRichard W.M. Jones 
7670a12ec87SRichard W.M. Jones     /* Check the remote host's key against known_hosts. */
768b10d49d7SPino Toscano     ret = check_host_key(s, opts->host_key_check, 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     /* Authenticate. */
774b10d49d7SPino Toscano     ret = authenticate(s, errp);
7750a12ec87SRichard W.M. Jones     if (ret < 0) {
7760a12ec87SRichard W.M. Jones         goto err;
7770a12ec87SRichard W.M. Jones     }
7780a12ec87SRichard W.M. Jones 
7790a12ec87SRichard W.M. Jones     /* Start SFTP. */
780b10d49d7SPino Toscano     s->sftp = sftp_new(s->session);
7810a12ec87SRichard W.M. Jones     if (!s->sftp) {
782b10d49d7SPino Toscano         session_error_setg(errp, s, "failed to create sftp handle");
783b10d49d7SPino Toscano         ret = -EINVAL;
784b10d49d7SPino Toscano         goto err;
785b10d49d7SPino Toscano     }
786b10d49d7SPino Toscano 
787b10d49d7SPino Toscano     r = sftp_init(s->sftp);
788b10d49d7SPino Toscano     if (r < 0) {
789b10d49d7SPino Toscano         sftp_error_setg(errp, s, "failed to initialize sftp handle");
7900a12ec87SRichard W.M. Jones         ret = -EINVAL;
7910a12ec87SRichard W.M. Jones         goto err;
7920a12ec87SRichard W.M. Jones     }
7930a12ec87SRichard W.M. Jones 
7940a12ec87SRichard W.M. Jones     /* Open the remote file. */
795023908a2SLaurent Vivier     trace_ssh_connect_to_ssh(opts->path, ssh_flags, creat_mode);
796b10d49d7SPino Toscano     s->sftp_handle = sftp_open(s->sftp, opts->path, ssh_flags, creat_mode);
7970a12ec87SRichard W.M. Jones     if (!s->sftp_handle) {
798b10d49d7SPino Toscano         sftp_error_setg(errp, s, "failed to open remote file '%s'",
79916e4bdb1SKevin Wolf                         opts->path);
8000a12ec87SRichard W.M. Jones         ret = -EINVAL;
8010a12ec87SRichard W.M. Jones         goto err;
8020a12ec87SRichard W.M. Jones     }
8030a12ec87SRichard W.M. Jones 
804b10d49d7SPino Toscano     /* Make sure the SFTP file is handled in blocking mode. */
805b10d49d7SPino Toscano     sftp_file_set_blocking(s->sftp_handle);
806b10d49d7SPino Toscano 
807b10d49d7SPino Toscano     s->attrs = sftp_fstat(s->sftp_handle);
808b10d49d7SPino Toscano     if (!s->attrs) {
8095496fb1aSMarkus Armbruster         sftp_error_setg(errp, s, "failed to read file attributes");
8100a12ec87SRichard W.M. Jones         return -EINVAL;
8110a12ec87SRichard W.M. Jones     }
8120a12ec87SRichard W.M. Jones 
8130a12ec87SRichard W.M. Jones     return 0;
8140a12ec87SRichard W.M. Jones 
8150a12ec87SRichard W.M. Jones  err:
816b10d49d7SPino Toscano     if (s->attrs) {
817b10d49d7SPino Toscano         sftp_attributes_free(s->attrs);
818b10d49d7SPino Toscano     }
819b10d49d7SPino Toscano     s->attrs = NULL;
8200a12ec87SRichard W.M. Jones     if (s->sftp_handle) {
821b10d49d7SPino Toscano         sftp_close(s->sftp_handle);
8220a12ec87SRichard W.M. Jones     }
8230a12ec87SRichard W.M. Jones     s->sftp_handle = NULL;
8240a12ec87SRichard W.M. Jones     if (s->sftp) {
825b10d49d7SPino Toscano         sftp_free(s->sftp);
8260a12ec87SRichard W.M. Jones     }
8270a12ec87SRichard W.M. Jones     s->sftp = NULL;
8280a12ec87SRichard W.M. Jones     if (s->session) {
829b10d49d7SPino Toscano         ssh_disconnect(s->session);
830b10d49d7SPino Toscano         ssh_free(s->session);
8310a12ec87SRichard W.M. Jones     }
8320a12ec87SRichard W.M. Jones     s->session = NULL;
833b10d49d7SPino Toscano     s->sock = -1;
834b10d49d7SPino Toscano     if (new_sock >= 0) {
835b10d49d7SPino Toscano         close(new_sock);
836b10d49d7SPino Toscano     }
8370a12ec87SRichard W.M. Jones 
8380a12ec87SRichard W.M. Jones     return ret;
8390a12ec87SRichard W.M. Jones }
8400a12ec87SRichard W.M. Jones 
ssh_open(BlockDriverState * bs,QDict * options,int bdrv_flags,Error ** errp)841d656aaa1SPaolo Bonzini static int ssh_open(BlockDriverState *bs, QDict *options, int bdrv_flags,
842015a1036SMax Reitz                     Error **errp)
8430a12ec87SRichard W.M. Jones {
8440a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
845375f0b92SKevin Wolf     BlockdevOptionsSsh *opts;
8460a12ec87SRichard W.M. Jones     int ret;
8470a12ec87SRichard W.M. Jones     int ssh_flags;
8480a12ec87SRichard W.M. Jones 
8490a12ec87SRichard W.M. Jones     ssh_state_init(s);
8500a12ec87SRichard W.M. Jones 
851b10d49d7SPino Toscano     ssh_flags = 0;
8520a12ec87SRichard W.M. Jones     if (bdrv_flags & BDRV_O_RDWR) {
853b10d49d7SPino Toscano         ssh_flags |= O_RDWR;
854b10d49d7SPino Toscano     } else {
855b10d49d7SPino Toscano         ssh_flags |= O_RDONLY;
8560a12ec87SRichard W.M. Jones     }
8570a12ec87SRichard W.M. Jones 
858375f0b92SKevin Wolf     opts = ssh_parse_options(options, errp);
859375f0b92SKevin Wolf     if (opts == NULL) {
860375f0b92SKevin Wolf         return -EINVAL;
861375f0b92SKevin Wolf     }
862375f0b92SKevin Wolf 
8630a12ec87SRichard W.M. Jones     /* Start up SSH. */
864375f0b92SKevin Wolf     ret = connect_to_ssh(s, opts, ssh_flags, 0, errp);
8650a12ec87SRichard W.M. Jones     if (ret < 0) {
8660a12ec87SRichard W.M. Jones         goto err;
8670a12ec87SRichard W.M. Jones     }
8680a12ec87SRichard W.M. Jones 
869be9c9404SEric Blake     if (s->attrs->type == SSH_FILEXFER_TYPE_REGULAR) {
870be9c9404SEric Blake         bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;
871be9c9404SEric Blake     }
872be9c9404SEric Blake 
873375f0b92SKevin Wolf     qapi_free_BlockdevOptionsSsh(opts);
874375f0b92SKevin Wolf 
8750a12ec87SRichard W.M. Jones     return 0;
8760a12ec87SRichard W.M. Jones 
8770a12ec87SRichard W.M. Jones  err:
878375f0b92SKevin Wolf     qapi_free_BlockdevOptionsSsh(opts);
879375f0b92SKevin Wolf 
8800a12ec87SRichard W.M. Jones     return ret;
8810a12ec87SRichard W.M. Jones }
8820a12ec87SRichard W.M. Jones 
883bd8e0e32SMax Reitz /* Note: This is a blocking operation */
ssh_grow_file(BDRVSSHState * s,int64_t offset,Error ** errp)8842b12a756SMax Reitz static int ssh_grow_file(BDRVSSHState *s, int64_t offset, Error **errp)
8852b12a756SMax Reitz {
8862b12a756SMax Reitz     ssize_t ret;
8872b12a756SMax Reitz     char c[1] = { '\0' };
888b10d49d7SPino Toscano     int was_blocking = ssh_is_blocking(s->session);
8892b12a756SMax Reitz 
8902b12a756SMax Reitz     /* offset must be strictly greater than the current size so we do
8912b12a756SMax Reitz      * not overwrite anything */
892b10d49d7SPino Toscano     assert(offset > 0 && offset > s->attrs->size);
8932b12a756SMax Reitz 
894b10d49d7SPino Toscano     ssh_set_blocking(s->session, 1);
895bd8e0e32SMax Reitz 
896b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset - 1);
897b10d49d7SPino Toscano     ret = sftp_write(s->sftp_handle, c, 1);
898bd8e0e32SMax Reitz 
899b10d49d7SPino Toscano     ssh_set_blocking(s->session, was_blocking);
900bd8e0e32SMax Reitz 
9012b12a756SMax Reitz     if (ret < 0) {
9022b12a756SMax Reitz         sftp_error_setg(errp, s, "Failed to grow file");
9032b12a756SMax Reitz         return -EIO;
9042b12a756SMax Reitz     }
9052b12a756SMax Reitz 
906b10d49d7SPino Toscano     s->attrs->size = offset;
9072b12a756SMax Reitz     return 0;
9082b12a756SMax Reitz }
9092b12a756SMax Reitz 
910766181feSChunyan Liu static QemuOptsList ssh_create_opts = {
911766181feSChunyan Liu     .name = "ssh-create-opts",
912766181feSChunyan Liu     .head = QTAILQ_HEAD_INITIALIZER(ssh_create_opts.head),
913766181feSChunyan Liu     .desc = {
9140a12ec87SRichard W.M. Jones         {
9150a12ec87SRichard W.M. Jones             .name = BLOCK_OPT_SIZE,
916766181feSChunyan Liu             .type = QEMU_OPT_SIZE,
9170a12ec87SRichard W.M. Jones             .help = "Virtual disk size"
9180a12ec87SRichard W.M. Jones         },
919766181feSChunyan Liu         { /* end of list */ }
920766181feSChunyan Liu     }
9210a12ec87SRichard W.M. Jones };
9220a12ec87SRichard W.M. Jones 
ssh_co_create(BlockdevCreateOptions * options,Error ** errp)9234906da7eSKevin Wolf static int ssh_co_create(BlockdevCreateOptions *options, Error **errp)
9240a12ec87SRichard W.M. Jones {
9254906da7eSKevin Wolf     BlockdevCreateOptionsSsh *opts = &options->u.ssh;
9260a12ec87SRichard W.M. Jones     BDRVSSHState s;
9274906da7eSKevin Wolf     int ret;
9284906da7eSKevin Wolf 
9294906da7eSKevin Wolf     assert(options->driver == BLOCKDEV_DRIVER_SSH);
9300a12ec87SRichard W.M. Jones 
9310a12ec87SRichard W.M. Jones     ssh_state_init(&s);
9320a12ec87SRichard W.M. Jones 
9334906da7eSKevin Wolf     ret = connect_to_ssh(&s, opts->location,
934b10d49d7SPino Toscano                          O_RDWR | O_CREAT | O_TRUNC,
9355496fb1aSMarkus Armbruster                          0644, errp);
9364906da7eSKevin Wolf     if (ret < 0) {
9374906da7eSKevin Wolf         goto fail;
9380a12ec87SRichard W.M. Jones     }
9390a12ec87SRichard W.M. Jones 
9404906da7eSKevin Wolf     if (opts->size > 0) {
9414906da7eSKevin Wolf         ret = ssh_grow_file(&s, opts->size, errp);
9422b12a756SMax Reitz         if (ret < 0) {
9434906da7eSKevin Wolf             goto fail;
9440a12ec87SRichard W.M. Jones         }
9450a12ec87SRichard W.M. Jones     }
9460a12ec87SRichard W.M. Jones 
9470a12ec87SRichard W.M. Jones     ret = 0;
9484906da7eSKevin Wolf fail:
9494906da7eSKevin Wolf     ssh_state_free(&s);
9504906da7eSKevin Wolf     return ret;
9514906da7eSKevin Wolf }
9524906da7eSKevin Wolf 
ssh_co_create_opts(BlockDriver * drv,const char * filename,QemuOpts * opts,Error ** errp)953b92902dfSMaxim Levitsky static int coroutine_fn ssh_co_create_opts(BlockDriver *drv,
954b92902dfSMaxim Levitsky                                            const char *filename,
955b92902dfSMaxim Levitsky                                            QemuOpts *opts,
9564906da7eSKevin Wolf                                            Error **errp)
9574906da7eSKevin Wolf {
9584906da7eSKevin Wolf     BlockdevCreateOptions *create_options;
9594906da7eSKevin Wolf     BlockdevCreateOptionsSsh *ssh_opts;
9604906da7eSKevin Wolf     int ret;
9614906da7eSKevin Wolf     QDict *uri_options = NULL;
9624906da7eSKevin Wolf 
9634906da7eSKevin Wolf     create_options = g_new0(BlockdevCreateOptions, 1);
9644906da7eSKevin Wolf     create_options->driver = BLOCKDEV_DRIVER_SSH;
9654906da7eSKevin Wolf     ssh_opts = &create_options->u.ssh;
9664906da7eSKevin Wolf 
9674906da7eSKevin Wolf     /* Get desired file size. */
9684906da7eSKevin Wolf     ssh_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
9694906da7eSKevin Wolf                               BDRV_SECTOR_SIZE);
970023908a2SLaurent Vivier     trace_ssh_co_create_opts(ssh_opts->size);
9714906da7eSKevin Wolf 
9724906da7eSKevin Wolf     uri_options = qdict_new();
9734906da7eSKevin Wolf     ret = parse_uri(filename, uri_options, errp);
9744906da7eSKevin Wolf     if (ret < 0) {
9754906da7eSKevin Wolf         goto out;
9764906da7eSKevin Wolf     }
9774906da7eSKevin Wolf 
9784906da7eSKevin Wolf     ssh_opts->location = ssh_parse_options(uri_options, errp);
9794906da7eSKevin Wolf     if (ssh_opts->location == NULL) {
9804906da7eSKevin Wolf         ret = -EINVAL;
9814906da7eSKevin Wolf         goto out;
9824906da7eSKevin Wolf     }
9834906da7eSKevin Wolf 
9844906da7eSKevin Wolf     ret = ssh_co_create(create_options, errp);
9850a12ec87SRichard W.M. Jones 
9860a12ec87SRichard W.M. Jones  out:
987cb3e7f08SMarc-André Lureau     qobject_unref(uri_options);
9884906da7eSKevin Wolf     qapi_free_BlockdevCreateOptions(create_options);
9890a12ec87SRichard W.M. Jones     return ret;
9900a12ec87SRichard W.M. Jones }
9910a12ec87SRichard W.M. Jones 
ssh_close(BlockDriverState * bs)9920a12ec87SRichard W.M. Jones static void ssh_close(BlockDriverState *bs)
9930a12ec87SRichard W.M. Jones {
9940a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
9950a12ec87SRichard W.M. Jones 
9960a12ec87SRichard W.M. Jones     ssh_state_free(s);
9970a12ec87SRichard W.M. Jones }
9980a12ec87SRichard W.M. Jones 
ssh_has_zero_init(BlockDriverState * bs)9990b3f21e6SRichard W.M. Jones static int ssh_has_zero_init(BlockDriverState *bs)
10000b3f21e6SRichard W.M. Jones {
10010b3f21e6SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
10020b3f21e6SRichard W.M. Jones     /* Assume false, unless we can positively prove it's true. */
10030b3f21e6SRichard W.M. Jones     int has_zero_init = 0;
10040b3f21e6SRichard W.M. Jones 
1005b10d49d7SPino Toscano     if (s->attrs->type == SSH_FILEXFER_TYPE_REGULAR) {
10060b3f21e6SRichard W.M. Jones         has_zero_init = 1;
10070b3f21e6SRichard W.M. Jones     }
10080b3f21e6SRichard W.M. Jones 
10090b3f21e6SRichard W.M. Jones     return has_zero_init;
10100b3f21e6SRichard W.M. Jones }
10110b3f21e6SRichard W.M. Jones 
10125aca18a4SPaolo Bonzini typedef struct BDRVSSHRestart {
10135aca18a4SPaolo Bonzini     BlockDriverState *bs;
10145aca18a4SPaolo Bonzini     Coroutine *co;
10155aca18a4SPaolo Bonzini } BDRVSSHRestart;
10165aca18a4SPaolo Bonzini 
restart_coroutine(void * opaque)10170a12ec87SRichard W.M. Jones static void restart_coroutine(void *opaque)
10180a12ec87SRichard W.M. Jones {
10195aca18a4SPaolo Bonzini     BDRVSSHRestart *restart = opaque;
10205aca18a4SPaolo Bonzini     BlockDriverState *bs = restart->bs;
10215aca18a4SPaolo Bonzini     BDRVSSHState *s = bs->opaque;
10225aca18a4SPaolo Bonzini     AioContext *ctx = bdrv_get_aio_context(bs);
10230a12ec87SRichard W.M. Jones 
1024023908a2SLaurent Vivier     trace_ssh_restart_coroutine(restart->co);
102560f782b6SStefan Hajnoczi     aio_set_fd_handler(ctx, s->sock, NULL, NULL, NULL, NULL, NULL);
10260a12ec87SRichard W.M. Jones 
10275aca18a4SPaolo Bonzini     aio_co_wake(restart->co);
10280a12ec87SRichard W.M. Jones }
10290a12ec87SRichard W.M. Jones 
10309d456654SPaolo Bonzini /* A non-blocking call returned EAGAIN, so yield, ensuring the
10319d456654SPaolo Bonzini  * handlers are set up so that we'll be rescheduled when there is an
10329d456654SPaolo Bonzini  * interesting event on the socket.
10339d456654SPaolo Bonzini  */
co_yield(BDRVSSHState * s,BlockDriverState * bs)10349d456654SPaolo Bonzini static coroutine_fn void co_yield(BDRVSSHState *s, BlockDriverState *bs)
10350a12ec87SRichard W.M. Jones {
10360a12ec87SRichard W.M. Jones     int r;
10370a12ec87SRichard W.M. Jones     IOHandler *rd_handler = NULL, *wr_handler = NULL;
10385aca18a4SPaolo Bonzini     BDRVSSHRestart restart = {
10395aca18a4SPaolo Bonzini         .bs = bs,
10405aca18a4SPaolo Bonzini         .co = qemu_coroutine_self()
10415aca18a4SPaolo Bonzini     };
10420a12ec87SRichard W.M. Jones 
1043b10d49d7SPino Toscano     r = ssh_get_poll_flags(s->session);
10440a12ec87SRichard W.M. Jones 
1045b10d49d7SPino Toscano     if (r & SSH_READ_PENDING) {
10460a12ec87SRichard W.M. Jones         rd_handler = restart_coroutine;
10470a12ec87SRichard W.M. Jones     }
1048b10d49d7SPino Toscano     if (r & SSH_WRITE_PENDING) {
10490a12ec87SRichard W.M. Jones         wr_handler = restart_coroutine;
10500a12ec87SRichard W.M. Jones     }
10510a12ec87SRichard W.M. Jones 
1052023908a2SLaurent Vivier     trace_ssh_co_yield(s->sock, rd_handler, wr_handler);
10530a12ec87SRichard W.M. Jones 
10542af0b200SStefan Hajnoczi     aio_set_fd_handler(bdrv_get_aio_context(bs), s->sock,
105560f782b6SStefan Hajnoczi                        rd_handler, wr_handler, NULL, NULL, &restart);
10560a12ec87SRichard W.M. Jones     qemu_coroutine_yield();
1057023908a2SLaurent Vivier     trace_ssh_co_yield_back(s->sock);
10580a12ec87SRichard W.M. Jones }
10590a12ec87SRichard W.M. Jones 
ssh_read(BDRVSSHState * s,BlockDriverState * bs,int64_t offset,size_t size,QEMUIOVector * qiov)10602af0b200SStefan Hajnoczi static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
10610a12ec87SRichard W.M. Jones                                  int64_t offset, size_t size,
10620a12ec87SRichard W.M. Jones                                  QEMUIOVector *qiov)
10630a12ec87SRichard W.M. Jones {
10640a12ec87SRichard W.M. Jones     ssize_t r;
10650a12ec87SRichard W.M. Jones     size_t got;
10660a12ec87SRichard W.M. Jones     char *buf, *end_of_vec;
10670a12ec87SRichard W.M. Jones     struct iovec *i;
10680a12ec87SRichard W.M. Jones 
1069023908a2SLaurent Vivier     trace_ssh_read(offset, size);
10700a12ec87SRichard W.M. Jones 
1071b10d49d7SPino Toscano     trace_ssh_seek(offset);
1072b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset);
10730a12ec87SRichard W.M. Jones 
10740a12ec87SRichard W.M. Jones     /* This keeps track of the current iovec element ('i'), where we
10750a12ec87SRichard W.M. Jones      * will write to next ('buf'), and the end of the current iovec
10760a12ec87SRichard W.M. Jones      * ('end_of_vec').
10770a12ec87SRichard W.M. Jones      */
10780a12ec87SRichard W.M. Jones     i = &qiov->iov[0];
10790a12ec87SRichard W.M. Jones     buf = i->iov_base;
10800a12ec87SRichard W.M. Jones     end_of_vec = i->iov_base + i->iov_len;
10810a12ec87SRichard W.M. Jones 
10820a12ec87SRichard W.M. Jones     for (got = 0; got < size; ) {
1083b10d49d7SPino Toscano         size_t request_read_size;
10840a12ec87SRichard W.M. Jones     again:
1085b10d49d7SPino Toscano         /*
1086b10d49d7SPino Toscano          * The size of SFTP packets is limited to 32K bytes, so limit
1087b10d49d7SPino Toscano          * the amount of data requested to 16K, as libssh currently
1088b10d49d7SPino Toscano          * does not handle multiple requests on its own.
1089b10d49d7SPino Toscano          */
1090b10d49d7SPino Toscano         request_read_size = MIN(end_of_vec - buf, 16384);
1091b10d49d7SPino Toscano         trace_ssh_read_buf(buf, end_of_vec - buf, request_read_size);
1092b10d49d7SPino Toscano         r = sftp_read(s->sftp_handle, buf, request_read_size);
1093b10d49d7SPino Toscano         trace_ssh_read_return(r, sftp_get_error(s->sftp));
10940a12ec87SRichard W.M. Jones 
1095b10d49d7SPino Toscano         if (r == SSH_AGAIN) {
10962af0b200SStefan Hajnoczi             co_yield(s, bs);
10970a12ec87SRichard W.M. Jones             goto again;
10980a12ec87SRichard W.M. Jones         }
1099b10d49d7SPino Toscano         if (r == SSH_EOF || (r == 0 && sftp_get_error(s->sftp) == SSH_FX_EOF)) {
11000a12ec87SRichard W.M. Jones             /* EOF: Short read so pad the buffer with zeroes and return it. */
11010a12ec87SRichard W.M. Jones             qemu_iovec_memset(qiov, got, 0, size - got);
11020a12ec87SRichard W.M. Jones             return 0;
11030a12ec87SRichard W.M. Jones         }
1104b10d49d7SPino Toscano         if (r <= 0) {
1105b10d49d7SPino Toscano             sftp_error_trace(s, "read");
1106b10d49d7SPino Toscano             return -EIO;
1107b10d49d7SPino Toscano         }
11080a12ec87SRichard W.M. Jones 
11090a12ec87SRichard W.M. Jones         got += r;
11100a12ec87SRichard W.M. Jones         buf += r;
11110a12ec87SRichard W.M. Jones         if (buf >= end_of_vec && got < size) {
11120a12ec87SRichard W.M. Jones             i++;
11130a12ec87SRichard W.M. Jones             buf = i->iov_base;
11140a12ec87SRichard W.M. Jones             end_of_vec = i->iov_base + i->iov_len;
11150a12ec87SRichard W.M. Jones         }
11160a12ec87SRichard W.M. Jones     }
11170a12ec87SRichard W.M. Jones 
11180a12ec87SRichard W.M. Jones     return 0;
11190a12ec87SRichard W.M. Jones }
11200a12ec87SRichard W.M. Jones 
ssh_co_readv(BlockDriverState * bs,int64_t sector_num,int nb_sectors,QEMUIOVector * qiov)11210a12ec87SRichard W.M. Jones static coroutine_fn int ssh_co_readv(BlockDriverState *bs,
11220a12ec87SRichard W.M. Jones                                      int64_t sector_num,
11230a12ec87SRichard W.M. Jones                                      int nb_sectors, QEMUIOVector *qiov)
11240a12ec87SRichard W.M. Jones {
11250a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
11260a12ec87SRichard W.M. Jones     int ret;
11270a12ec87SRichard W.M. Jones 
11280a12ec87SRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
11292af0b200SStefan Hajnoczi     ret = ssh_read(s, bs, sector_num * BDRV_SECTOR_SIZE,
11300a12ec87SRichard W.M. Jones                    nb_sectors * BDRV_SECTOR_SIZE, qiov);
11310a12ec87SRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
11320a12ec87SRichard W.M. Jones 
11330a12ec87SRichard W.M. Jones     return ret;
11340a12ec87SRichard W.M. Jones }
11350a12ec87SRichard W.M. Jones 
ssh_write(BDRVSSHState * s,BlockDriverState * bs,int64_t offset,size_t size,QEMUIOVector * qiov)113642f6ad79SAlberto Faria static coroutine_fn int ssh_write(BDRVSSHState *s, BlockDriverState *bs,
11370a12ec87SRichard W.M. Jones                                   int64_t offset, size_t size,
11380a12ec87SRichard W.M. Jones                                   QEMUIOVector *qiov)
11390a12ec87SRichard W.M. Jones {
11400a12ec87SRichard W.M. Jones     ssize_t r;
11410a12ec87SRichard W.M. Jones     size_t written;
11420a12ec87SRichard W.M. Jones     char *buf, *end_of_vec;
11430a12ec87SRichard W.M. Jones     struct iovec *i;
11440a12ec87SRichard W.M. Jones 
1145023908a2SLaurent Vivier     trace_ssh_write(offset, size);
11460a12ec87SRichard W.M. Jones 
1147b10d49d7SPino Toscano     trace_ssh_seek(offset);
1148b10d49d7SPino Toscano     sftp_seek64(s->sftp_handle, offset);
11490a12ec87SRichard W.M. Jones 
11500a12ec87SRichard W.M. Jones     /* This keeps track of the current iovec element ('i'), where we
11510a12ec87SRichard W.M. Jones      * will read from next ('buf'), and the end of the current iovec
11520a12ec87SRichard W.M. Jones      * ('end_of_vec').
11530a12ec87SRichard W.M. Jones      */
11540a12ec87SRichard W.M. Jones     i = &qiov->iov[0];
11550a12ec87SRichard W.M. Jones     buf = i->iov_base;
11560a12ec87SRichard W.M. Jones     end_of_vec = i->iov_base + i->iov_len;
11570a12ec87SRichard W.M. Jones 
11580a12ec87SRichard W.M. Jones     for (written = 0; written < size; ) {
1159b10d49d7SPino Toscano         size_t request_write_size;
11600a12ec87SRichard W.M. Jones     again:
1161b10d49d7SPino Toscano         /*
1162b10d49d7SPino Toscano          * Avoid too large data packets, as libssh currently does not
1163b10d49d7SPino Toscano          * handle multiple requests on its own.
1164b10d49d7SPino Toscano          */
1165b10d49d7SPino Toscano         request_write_size = MIN(end_of_vec - buf, 131072);
1166b10d49d7SPino Toscano         trace_ssh_write_buf(buf, end_of_vec - buf, request_write_size);
1167b10d49d7SPino Toscano         r = sftp_write(s->sftp_handle, buf, request_write_size);
1168b10d49d7SPino Toscano         trace_ssh_write_return(r, sftp_get_error(s->sftp));
11690a12ec87SRichard W.M. Jones 
1170b10d49d7SPino Toscano         if (r == SSH_AGAIN) {
11712af0b200SStefan Hajnoczi             co_yield(s, bs);
11720a12ec87SRichard W.M. Jones             goto again;
11730a12ec87SRichard W.M. Jones         }
11740a12ec87SRichard W.M. Jones         if (r < 0) {
11756b3048ceSMarkus Armbruster             sftp_error_trace(s, "write");
11760a12ec87SRichard W.M. Jones             return -EIO;
11770a12ec87SRichard W.M. Jones         }
11780a12ec87SRichard W.M. Jones 
11790a12ec87SRichard W.M. Jones         written += r;
11800a12ec87SRichard W.M. Jones         buf += r;
11810a12ec87SRichard W.M. Jones         if (buf >= end_of_vec && written < size) {
11820a12ec87SRichard W.M. Jones             i++;
11830a12ec87SRichard W.M. Jones             buf = i->iov_base;
11840a12ec87SRichard W.M. Jones             end_of_vec = i->iov_base + i->iov_len;
11850a12ec87SRichard W.M. Jones         }
11860a12ec87SRichard W.M. Jones 
1187b10d49d7SPino Toscano         if (offset + written > s->attrs->size) {
1188b10d49d7SPino Toscano             s->attrs->size = offset + written;
1189b10d49d7SPino Toscano         }
11900a12ec87SRichard W.M. Jones     }
11910a12ec87SRichard W.M. Jones 
11920a12ec87SRichard W.M. Jones     return 0;
11930a12ec87SRichard W.M. Jones }
11940a12ec87SRichard W.M. Jones 
ssh_co_writev(BlockDriverState * bs,int64_t sector_num,int nb_sectors,QEMUIOVector * qiov,int flags)11950a12ec87SRichard W.M. Jones static coroutine_fn int ssh_co_writev(BlockDriverState *bs,
11960a12ec87SRichard W.M. Jones                                       int64_t sector_num,
1197e18a58b4SEric Blake                                       int nb_sectors, QEMUIOVector *qiov,
1198e18a58b4SEric Blake                                       int flags)
11990a12ec87SRichard W.M. Jones {
12000a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
12010a12ec87SRichard W.M. Jones     int ret;
12020a12ec87SRichard W.M. Jones 
12030a12ec87SRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
12042af0b200SStefan Hajnoczi     ret = ssh_write(s, bs, sector_num * BDRV_SECTOR_SIZE,
12050a12ec87SRichard W.M. Jones                     nb_sectors * BDRV_SECTOR_SIZE, qiov);
12060a12ec87SRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
12070a12ec87SRichard W.M. Jones 
12080a12ec87SRichard W.M. Jones     return ret;
12090a12ec87SRichard W.M. Jones }
12100a12ec87SRichard W.M. Jones 
unsafe_flush_warning(BDRVSSHState * s,const char * what)12119a2d462eSRichard W.M. Jones static void unsafe_flush_warning(BDRVSSHState *s, const char *what)
12129a2d462eSRichard W.M. Jones {
12139a2d462eSRichard W.M. Jones     if (!s->unsafe_flush_warning) {
12143dc6f869SAlistair Francis         warn_report("ssh server %s does not support fsync",
12150da5b8efSAshijeet Acharya                     s->inet->host);
12169a2d462eSRichard W.M. Jones         if (what) {
12179a2d462eSRichard W.M. Jones             error_report("to support fsync, you need %s", what);
12189a2d462eSRichard W.M. Jones         }
12199a2d462eSRichard W.M. Jones         s->unsafe_flush_warning = true;
12209a2d462eSRichard W.M. Jones     }
12219a2d462eSRichard W.M. Jones }
12229a2d462eSRichard W.M. Jones 
ssh_flush(BDRVSSHState * s,BlockDriverState * bs)12232af0b200SStefan Hajnoczi static coroutine_fn int ssh_flush(BDRVSSHState *s, BlockDriverState *bs)
12249a2d462eSRichard W.M. Jones {
12259a2d462eSRichard W.M. Jones     int r;
12269a2d462eSRichard W.M. Jones 
1227023908a2SLaurent Vivier     trace_ssh_flush();
1228b10d49d7SPino Toscano 
1229b10d49d7SPino Toscano     if (!sftp_extension_supported(s->sftp, "fsync@openssh.com", "1")) {
12309a2d462eSRichard W.M. Jones         unsafe_flush_warning(s, "OpenSSH >= 6.3");
12319a2d462eSRichard W.M. Jones         return 0;
12329a2d462eSRichard W.M. Jones     }
1233b10d49d7SPino Toscano  again:
1234b10d49d7SPino Toscano     r = sftp_fsync(s->sftp_handle);
1235b10d49d7SPino Toscano     if (r == SSH_AGAIN) {
1236b10d49d7SPino Toscano         co_yield(s, bs);
1237b10d49d7SPino Toscano         goto again;
1238b10d49d7SPino Toscano     }
12399a2d462eSRichard W.M. Jones     if (r < 0) {
12406b3048ceSMarkus Armbruster         sftp_error_trace(s, "fsync");
12419a2d462eSRichard W.M. Jones         return -EIO;
12429a2d462eSRichard W.M. Jones     }
12439a2d462eSRichard W.M. Jones 
12449a2d462eSRichard W.M. Jones     return 0;
12459a2d462eSRichard W.M. Jones }
12469a2d462eSRichard W.M. Jones 
ssh_co_flush(BlockDriverState * bs)12479a2d462eSRichard W.M. Jones static coroutine_fn int ssh_co_flush(BlockDriverState *bs)
12489a2d462eSRichard W.M. Jones {
12499a2d462eSRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
12509a2d462eSRichard W.M. Jones     int ret;
12519a2d462eSRichard W.M. Jones 
12529a2d462eSRichard W.M. Jones     qemu_co_mutex_lock(&s->lock);
12532af0b200SStefan Hajnoczi     ret = ssh_flush(s, bs);
12549a2d462eSRichard W.M. Jones     qemu_co_mutex_unlock(&s->lock);
12559a2d462eSRichard W.M. Jones 
12569a2d462eSRichard W.M. Jones     return ret;
12579a2d462eSRichard W.M. Jones }
12589a2d462eSRichard W.M. Jones 
ssh_co_getlength(BlockDriverState * bs)1259c86422c5SEmanuele Giuseppe Esposito static int64_t coroutine_fn ssh_co_getlength(BlockDriverState *bs)
12600a12ec87SRichard W.M. Jones {
12610a12ec87SRichard W.M. Jones     BDRVSSHState *s = bs->opaque;
12620a12ec87SRichard W.M. Jones     int64_t length;
12630a12ec87SRichard W.M. Jones 
1264b10d49d7SPino Toscano     /* Note we cannot make a libssh call here. */
1265b10d49d7SPino Toscano     length = (int64_t) s->attrs->size;
1266023908a2SLaurent Vivier     trace_ssh_getlength(length);
12670a12ec87SRichard W.M. Jones 
12680a12ec87SRichard W.M. Jones     return length;
12690a12ec87SRichard W.M. Jones }
12700a12ec87SRichard W.M. Jones 
ssh_co_truncate(BlockDriverState * bs,int64_t offset,bool exact,PreallocMode prealloc,BdrvRequestFlags flags,Error ** errp)1271061ca8a3SKevin Wolf static int coroutine_fn ssh_co_truncate(BlockDriverState *bs, int64_t offset,
1272c80d8b06SMax Reitz                                         bool exact, PreallocMode prealloc,
127392b92799SKevin Wolf                                         BdrvRequestFlags flags, Error **errp)
1274624f3006SMax Reitz {
1275624f3006SMax Reitz     BDRVSSHState *s = bs->opaque;
1276624f3006SMax Reitz 
1277624f3006SMax Reitz     if (prealloc != PREALLOC_MODE_OFF) {
1278624f3006SMax Reitz         error_setg(errp, "Unsupported preallocation mode '%s'",
1279624f3006SMax Reitz                    PreallocMode_str(prealloc));
1280624f3006SMax Reitz         return -ENOTSUP;
1281624f3006SMax Reitz     }
1282624f3006SMax Reitz 
1283b10d49d7SPino Toscano     if (offset < s->attrs->size) {
1284624f3006SMax Reitz         error_setg(errp, "ssh driver does not support shrinking files");
1285624f3006SMax Reitz         return -ENOTSUP;
1286624f3006SMax Reitz     }
1287624f3006SMax Reitz 
1288b10d49d7SPino Toscano     if (offset == s->attrs->size) {
1289624f3006SMax Reitz         return 0;
1290624f3006SMax Reitz     }
1291624f3006SMax Reitz 
1292624f3006SMax Reitz     return ssh_grow_file(s, offset, errp);
1293624f3006SMax Reitz }
1294624f3006SMax Reitz 
ssh_refresh_filename(BlockDriverState * bs)1295b8c1f901SMax Reitz static void ssh_refresh_filename(BlockDriverState *bs)
1296b8c1f901SMax Reitz {
1297b8c1f901SMax Reitz     BDRVSSHState *s = bs->opaque;
1298b8c1f901SMax Reitz     const char *path, *host_key_check;
1299b8c1f901SMax Reitz     int ret;
1300b8c1f901SMax Reitz 
1301b8c1f901SMax Reitz     /*
1302b8c1f901SMax Reitz      * None of these options can be represented in a plain "host:port"
1303b8c1f901SMax Reitz      * format, so if any was given, we have to abort.
1304b8c1f901SMax Reitz      */
1305b8c1f901SMax Reitz     if (s->inet->has_ipv4 || s->inet->has_ipv6 || s->inet->has_to ||
1306b8c1f901SMax Reitz         s->inet->has_numeric)
1307b8c1f901SMax Reitz     {
1308b8c1f901SMax Reitz         return;
1309b8c1f901SMax Reitz     }
1310b8c1f901SMax Reitz 
1311b8c1f901SMax Reitz     path = qdict_get_try_str(bs->full_open_options, "path");
1312b8c1f901SMax Reitz     assert(path); /* mandatory option */
1313b8c1f901SMax Reitz 
1314b8c1f901SMax Reitz     host_key_check = qdict_get_try_str(bs->full_open_options, "host_key_check");
1315b8c1f901SMax Reitz 
1316b8c1f901SMax Reitz     ret = snprintf(bs->exact_filename, sizeof(bs->exact_filename),
1317b8c1f901SMax Reitz                    "ssh://%s@%s:%s%s%s%s",
1318b8c1f901SMax Reitz                    s->user, s->inet->host, s->inet->port, path,
1319b8c1f901SMax Reitz                    host_key_check ? "?host_key_check=" : "",
1320b8c1f901SMax Reitz                    host_key_check ?: "");
1321b8c1f901SMax Reitz     if (ret >= sizeof(bs->exact_filename)) {
1322b8c1f901SMax Reitz         /* An overflow makes the filename unusable, so do not report any */
1323b8c1f901SMax Reitz         bs->exact_filename[0] = '\0';
1324b8c1f901SMax Reitz     }
1325b8c1f901SMax Reitz }
1326b8c1f901SMax Reitz 
ssh_bdrv_dirname(BlockDriverState * bs,Error ** errp)132721205c7cSMax Reitz static char *ssh_bdrv_dirname(BlockDriverState *bs, Error **errp)
132821205c7cSMax Reitz {
132921205c7cSMax Reitz     if (qdict_haskey(bs->full_open_options, "host_key_check")) {
133021205c7cSMax Reitz         /*
133121205c7cSMax Reitz          * We cannot generate a simple prefix if we would have to
133221205c7cSMax Reitz          * append a query string.
133321205c7cSMax Reitz          */
133421205c7cSMax Reitz         error_setg(errp,
133521205c7cSMax Reitz                    "Cannot generate a base directory with host_key_check set");
133621205c7cSMax Reitz         return NULL;
133721205c7cSMax Reitz     }
133821205c7cSMax Reitz 
133921205c7cSMax Reitz     if (bs->exact_filename[0] == '\0') {
134021205c7cSMax Reitz         error_setg(errp, "Cannot generate a base directory for this ssh node");
134121205c7cSMax Reitz         return NULL;
134221205c7cSMax Reitz     }
134321205c7cSMax Reitz 
134421205c7cSMax Reitz     return path_combine(bs->exact_filename, "");
134521205c7cSMax Reitz }
134621205c7cSMax Reitz 
13472654267cSMax Reitz static const char *const ssh_strong_runtime_opts[] = {
13482654267cSMax Reitz     "host",
13492654267cSMax Reitz     "port",
13502654267cSMax Reitz     "path",
13512654267cSMax Reitz     "user",
13522654267cSMax Reitz     "host_key_check",
13532654267cSMax Reitz     "server.",
13542654267cSMax Reitz 
13552654267cSMax Reitz     NULL
13562654267cSMax Reitz };
13572654267cSMax Reitz 
13580a12ec87SRichard W.M. Jones static BlockDriver bdrv_ssh = {
13590a12ec87SRichard W.M. Jones     .format_name                  = "ssh",
13600a12ec87SRichard W.M. Jones     .protocol_name                = "ssh",
13610a12ec87SRichard W.M. Jones     .instance_size                = sizeof(BDRVSSHState),
13620a12ec87SRichard W.M. Jones     .bdrv_parse_filename          = ssh_parse_filename,
1363d656aaa1SPaolo Bonzini     .bdrv_open                    = ssh_open,
13644906da7eSKevin Wolf     .bdrv_co_create               = ssh_co_create,
1365efc75e2aSStefan Hajnoczi     .bdrv_co_create_opts          = ssh_co_create_opts,
13660a12ec87SRichard W.M. Jones     .bdrv_close                   = ssh_close,
13670b3f21e6SRichard W.M. Jones     .bdrv_has_zero_init           = ssh_has_zero_init,
13680a12ec87SRichard W.M. Jones     .bdrv_co_readv                = ssh_co_readv,
13690a12ec87SRichard W.M. Jones     .bdrv_co_writev               = ssh_co_writev,
1370c86422c5SEmanuele Giuseppe Esposito     .bdrv_co_getlength            = ssh_co_getlength,
1371061ca8a3SKevin Wolf     .bdrv_co_truncate             = ssh_co_truncate,
13729a2d462eSRichard W.M. Jones     .bdrv_co_flush_to_disk        = ssh_co_flush,
1373b8c1f901SMax Reitz     .bdrv_refresh_filename        = ssh_refresh_filename,
137421205c7cSMax Reitz     .bdrv_dirname                 = ssh_bdrv_dirname,
1375766181feSChunyan Liu     .create_opts                  = &ssh_create_opts,
13762654267cSMax Reitz     .strong_runtime_opts          = ssh_strong_runtime_opts,
13770a12ec87SRichard W.M. Jones };
13780a12ec87SRichard W.M. Jones 
bdrv_ssh_init(void)13790a12ec87SRichard W.M. Jones static void bdrv_ssh_init(void)
13800a12ec87SRichard W.M. Jones {
13810a12ec87SRichard W.M. Jones     int r;
13820a12ec87SRichard W.M. Jones 
1383b10d49d7SPino Toscano     r = ssh_init();
13840a12ec87SRichard W.M. Jones     if (r != 0) {
1385b10d49d7SPino Toscano         fprintf(stderr, "libssh initialization failed, %d\n", r);
13860a12ec87SRichard W.M. Jones         exit(EXIT_FAILURE);
13870a12ec87SRichard W.M. Jones     }
13880a12ec87SRichard W.M. Jones 
1389b10d49d7SPino Toscano #if TRACE_LIBSSH != 0
1390b10d49d7SPino Toscano     ssh_set_log_level(TRACE_LIBSSH);
1391b10d49d7SPino Toscano #endif
1392b10d49d7SPino Toscano 
13930a12ec87SRichard W.M. Jones     bdrv_register(&bdrv_ssh);
13940a12ec87SRichard W.M. Jones }
13950a12ec87SRichard W.M. Jones 
13960a12ec87SRichard W.M. Jones block_init(bdrv_ssh_init);
1397