1 /*
2  * virnetsshsession.c: ssh network transport provider based on libssh2
3  *
4  * Copyright (C) 2012-2013 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 #include <config.h>
21 #include <libssh2.h>
22 #include <libssh2_publickey.h>
23 
24 #include "virnetsshsession.h"
25 
26 #include "internal.h"
27 #include "virbuffer.h"
28 #include "viralloc.h"
29 #include "virlog.h"
30 #include "configmake.h"
31 #include "virthread.h"
32 #include "virerror.h"
33 #include "virfile.h"
34 #include "virobject.h"
35 #include "virstring.h"
36 #include "virauth.h"
37 
38 #define VIR_FROM_THIS VIR_FROM_SSH
39 
40 VIR_LOG_INIT("rpc.netsshsession");
41 
42 static const char
43 vir_libssh2_key_comment[] = "added by libvirt ssh transport";
44 #define VIR_NET_SSH_BUFFER_SIZE  1024
45 
46 typedef enum {
47     VIR_NET_SSH_STATE_NEW,
48     VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE,
49     VIR_NET_SSH_STATE_AUTH_CALLBACK_ERROR,
50     VIR_NET_SSH_STATE_CLOSED,
51     VIR_NET_SSH_STATE_ERROR,
52     VIR_NET_SSH_STATE_ERROR_REMOTE,
53 } virNetSSHSessionState;
54 
55 typedef enum {
56     VIR_NET_SSH_AUTHCB_OK,
57     VIR_NET_SSH_AUTHCB_NO_METHOD,
58     VIR_NET_SSH_AUTHCB_OOM,
59     VIR_NET_SSH_AUTHCB_RETR_ERR,
60 } virNetSSHAuthCallbackError;
61 
62 typedef enum {
63     VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE,
64     VIR_NET_SSH_AUTH_PASSWORD,
65     VIR_NET_SSH_AUTH_PRIVKEY,
66     VIR_NET_SSH_AUTH_AGENT
67 } virNetSSHAuthMethods;
68 
69 
70 typedef struct _virNetSSHAuthMethod virNetSSHAuthMethod;
71 
72 struct _virNetSSHAuthMethod {
73     virNetSSHAuthMethods method;
74     char *username;
75     char *password;
76     char *filename;
77 
78     int tries;
79 };
80 
81 struct _virNetSSHSession {
82     virObjectLockable parent;
83     virNetSSHSessionState state;
84 
85     /* libssh2 internal stuff */
86     LIBSSH2_SESSION *session;
87     LIBSSH2_CHANNEL *channel;
88     LIBSSH2_KNOWNHOSTS *knownHosts;
89     LIBSSH2_AGENT *agent;
90 
91     /* for host key checking */
92     virNetSSHHostkeyVerify hostKeyVerify;
93     char *knownHostsFile;
94     char *hostname;
95     int port;
96 
97     /* authentication stuff */
98     virConnectAuthPtr cred;
99     char *authPath;
100     virNetSSHAuthCallbackError authCbErr;
101     size_t nauths;
102     virNetSSHAuthMethod **auths;
103 
104     /* channel stuff */
105     char *channelCommand;
106     int channelCommandReturnValue;
107 
108     /* read cache */
109     char rbuf[VIR_NET_SSH_BUFFER_SIZE];
110     size_t bufUsed;
111     size_t bufStart;
112 };
113 
114 static void
virNetSSHSessionAuthMethodsClear(virNetSSHSession * sess)115 virNetSSHSessionAuthMethodsClear(virNetSSHSession *sess)
116 {
117     size_t i;
118 
119     for (i = 0; i < sess->nauths; i++) {
120         VIR_FREE(sess->auths[i]->username);
121         VIR_FREE(sess->auths[i]->password);
122         VIR_FREE(sess->auths[i]->filename);
123         VIR_FREE(sess->auths[i]);
124     }
125 
126     VIR_FREE(sess->auths);
127     sess->nauths = 0;
128 }
129 
130 static void
virNetSSHSessionDispose(void * obj)131 virNetSSHSessionDispose(void *obj)
132 {
133     virNetSSHSession *sess = obj;
134     VIR_DEBUG("sess=0x%p", sess);
135 
136     if (sess->channel) {
137         libssh2_channel_send_eof(sess->channel);
138         libssh2_channel_close(sess->channel);
139         libssh2_channel_free(sess->channel);
140     }
141 
142     libssh2_knownhost_free(sess->knownHosts);
143     libssh2_agent_free(sess->agent);
144 
145     if (sess->session) {
146         libssh2_session_disconnect(sess->session,
147                                    "libvirt: virNetSSHSessionFree()");
148         libssh2_session_free(sess->session);
149     }
150 
151     virNetSSHSessionAuthMethodsClear(sess);
152 
153     g_free(sess->channelCommand);
154     g_free(sess->hostname);
155     g_free(sess->knownHostsFile);
156     g_free(sess->authPath);
157 }
158 
159 static virClass *virNetSSHSessionClass;
160 static int
virNetSSHSessionOnceInit(void)161 virNetSSHSessionOnceInit(void)
162 {
163     if (!VIR_CLASS_NEW(virNetSSHSession, virClassForObjectLockable()))
164         return -1;
165 
166     return 0;
167 }
168 VIR_ONCE_GLOBAL_INIT(virNetSSHSession);
169 
170 static virNetSSHAuthMethod *
virNetSSHSessionAuthMethodNew(virNetSSHSession * sess)171 virNetSSHSessionAuthMethodNew(virNetSSHSession *sess)
172 {
173     virNetSSHAuthMethod *auth;
174 
175     auth = g_new0(virNetSSHAuthMethod, 1);
176 
177     VIR_EXPAND_N(sess->auths, sess->nauths, 1);
178     sess->auths[sess->nauths - 1] = auth;
179 
180     return auth;
181 }
182 
183 /* keyboard interactive authentication callback */
184 static void
virNetSSHKbIntCb(const char * name G_GNUC_UNUSED,int name_len G_GNUC_UNUSED,const char * instruction G_GNUC_UNUSED,int instruction_len G_GNUC_UNUSED,int num_prompts,const LIBSSH2_USERAUTH_KBDINT_PROMPT * prompts,LIBSSH2_USERAUTH_KBDINT_RESPONSE * responses,void ** opaque)185 virNetSSHKbIntCb(const char *name G_GNUC_UNUSED,
186                  int name_len G_GNUC_UNUSED,
187                  const char *instruction G_GNUC_UNUSED,
188                  int instruction_len G_GNUC_UNUSED,
189                  int num_prompts,
190                  const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
191                  LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
192                  void **opaque)
193 {
194     virNetSSHSession *priv = *opaque;
195     virConnectCredentialPtr askcred = NULL;
196     size_t i;
197     int credtype_echo = -1;
198     int credtype_noecho = -1;
199     char *tmp;
200 
201     priv->authCbErr = VIR_NET_SSH_AUTHCB_OK;
202 
203     /* find credential type for asking passwords */
204     for (i = 0; i < priv->cred->ncredtype; i++) {
205         if (priv->cred->credtype[i] == VIR_CRED_PASSPHRASE ||
206             priv->cred->credtype[i] == VIR_CRED_NOECHOPROMPT)
207             credtype_noecho = priv->cred->credtype[i];
208 
209         if (priv->cred->credtype[i] == VIR_CRED_ECHOPROMPT)
210             credtype_echo = priv->cred->credtype[i];
211     }
212 
213     if (credtype_echo < 0 || credtype_noecho < 0) {
214         priv->authCbErr = VIR_NET_SSH_AUTHCB_NO_METHOD;
215         return;
216     }
217 
218     askcred = g_new0(virConnectCredential, num_prompts);
219 
220     /* fill data structures for auth callback */
221     for (i = 0; i < num_prompts; i++) {
222         char *prompt;
223         prompt = g_strdup(prompts[i].text);
224         askcred[i].prompt = prompt;
225 
226         /* remove colon and trailing spaces from prompts, as default behavior
227          * of libvirt's auth callback is to add them */
228         if ((tmp = strrchr(askcred[i].prompt, ':')))
229             *tmp = '\0';
230 
231         askcred[i].type = prompts[i].echo ? credtype_echo : credtype_noecho;
232     }
233 
234     /* retrieve responses using the auth callback */
235     if (priv->cred->cb(askcred, num_prompts, priv->cred->cbdata)) {
236         priv->authCbErr = VIR_NET_SSH_AUTHCB_RETR_ERR;
237         goto cleanup;
238     }
239 
240     /* copy retrieved data back */
241     for (i = 0; i < num_prompts; i++) {
242         responses[i].text = g_steal_pointer(&askcred[i].result); /* steal the pointer */
243         responses[i].length = askcred[i].resultlen;
244     }
245 
246  cleanup:
247     if (askcred) {
248         for (i = 0; i < num_prompts; i++) {
249             char *prompt = (char *)askcred[i].prompt;
250             VIR_FREE(askcred[i].result);
251             VIR_FREE(prompt);
252         }
253     }
254 
255     VIR_FREE(askcred);
256 
257     return;
258 }
259 
260 /* check session host keys
261  *
262  * this function checks the known host database and verifies the key
263  * errors are raised in this func
264  *
265  * return value: 0 on success, -1 on error
266  */
267 static int
virNetSSHCheckHostKey(virNetSSHSession * sess)268 virNetSSHCheckHostKey(virNetSSHSession *sess)
269 {
270     int ret;
271     const char *key;
272     const char *keyhash;
273     char *keyhashstr;
274     char *tmp;
275     int keyType;
276     size_t keyLength;
277     char *errmsg;
278     g_auto(virBuffer) buff = VIR_BUFFER_INITIALIZER;
279     virConnectCredential askKey;
280     struct libssh2_knownhost *knownHostEntry = NULL;
281     size_t i;
282     char *hostnameStr = NULL;
283 
284     if (sess->hostKeyVerify == VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE)
285         return 0;
286 
287     /* get the key */
288     key = libssh2_session_hostkey(sess->session, &keyLength, &keyType);
289     if (!key) {
290         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
291         virReportError(VIR_ERR_SSH,
292                        _("Failed to retrieve ssh host key: %s"),
293                        errmsg);
294         return -1;
295     }
296 
297     /* verify it */
298     ret = libssh2_knownhost_checkp(sess->knownHosts,
299                                    sess->hostname,
300                                    sess->port,
301                                    key,
302                                    keyLength,
303                                    LIBSSH2_KNOWNHOST_TYPE_PLAIN |
304                                    LIBSSH2_KNOWNHOST_KEYENC_RAW,
305                                    &knownHostEntry);
306 
307     switch (ret) {
308     case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
309         /* key was not found, query to add it to database */
310         if (sess->hostKeyVerify == VIR_NET_SSH_HOSTKEY_VERIFY_NORMAL) {
311             /* ask to add the key */
312             if (!sess->cred || !sess->cred->cb) {
313                 virReportError(VIR_ERR_SSH, "%s",
314                                _("No user interaction callback provided: "
315                                  "Can't verify the session host key"));
316                 return -1;
317             }
318 
319             /* prepare data for the callback */
320             memset(&askKey, 0, sizeof(virConnectCredential));
321 
322             for (i = 0; i < sess->cred->ncredtype; i++) {
323                 if (sess->cred->credtype[i] == VIR_CRED_ECHOPROMPT)
324                     break;
325             }
326 
327             if (i == sess->cred->ncredtype) {
328                 virReportError(VIR_ERR_SSH, "%s",
329                                _("no suitable callback for host key "
330                                  "verification"));
331                 return -1;
332             }
333 
334             /* calculate remote key hash, using MD5 algorithm that is
335              * usual in OpenSSH. The returned value should *NOT* be freed */
336             if (!(keyhash = libssh2_hostkey_hash(sess->session,
337                                                  LIBSSH2_HOSTKEY_HASH_MD5))) {
338                 virReportError(VIR_ERR_SSH, "%s",
339                                _("failed to calculate ssh host key hash"));
340                 return -1;
341             }
342             /* format the host key into a nice userfriendly string.
343              * Sadly, there's no constant to describe the hash length, so
344              * we have to use a *MAGIC* constant. */
345             for (i = 0; i < 16; i++)
346                 virBufferAsprintf(&buff, "%02hhX:", keyhash[i]);
347             virBufferTrim(&buff, ":");
348 
349             keyhashstr = virBufferContentAndReset(&buff);
350 
351             askKey.type = VIR_CRED_ECHOPROMPT;
352             askKey.prompt = g_strdup_printf(_("Accept SSH host key with hash '%s' for " "host '%s:%d' (%s/%s)?"),
353                                             keyhashstr, sess->hostname, sess->port, "y", "n");
354 
355             if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) {
356                 virReportError(VIR_ERR_SSH, "%s",
357                                _("failed to retrieve decision to accept "
358                                  "host key"));
359                 tmp = (char*)askKey.prompt;
360                 VIR_FREE(tmp);
361                 VIR_FREE(keyhashstr);
362                 return -1;
363             }
364 
365             tmp = (char*)askKey.prompt;
366             VIR_FREE(tmp);
367 
368             if (!askKey.result ||
369                 STRCASENEQ(askKey.result, "y")) {
370                 virReportError(VIR_ERR_SSH,
371                                _("SSH host key for '%s' (%s) was not accepted"),
372                                sess->hostname, keyhashstr);
373                 VIR_FREE(keyhashstr);
374                 VIR_FREE(askKey.result);
375                 return -1;
376             }
377             VIR_FREE(keyhashstr);
378             VIR_FREE(askKey.result);
379         }
380 
381         /* VIR_NET_SSH_HOSTKEY_VERIFY_AUTO_ADD */
382         /* convert key type, as libssh is using different enums type for
383          * getting the key and different for adding ... */
384         switch (keyType) {
385         case LIBSSH2_HOSTKEY_TYPE_RSA:
386             keyType = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
387             break;
388         case LIBSSH2_HOSTKEY_TYPE_DSS:
389             keyType = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
390             break;
391 #ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
392         /* defs from libssh2 v1.9.0 or later */
393         case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
394             keyType = LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
395             break;
396         case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
397             keyType = LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
398             break;
399         case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
400             keyType = LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
401             break;
402         case LIBSSH2_HOSTKEY_TYPE_ED25519:
403             keyType = LIBSSH2_KNOWNHOST_KEY_ED25519;
404             break;
405 #endif
406         case LIBSSH2_HOSTKEY_TYPE_UNKNOWN:
407         default:
408             virReportError(VIR_ERR_SSH, "%s",
409                            _("unsupported SSH key type"));
410             return -1;
411         }
412 
413         /* add the key to the DB and save it, if applicable */
414         /* construct a "[hostname]:port" string to have the hostkey bound
415          * to port number */
416         virBufferAsprintf(&buff, "[%s]:%d", sess->hostname, sess->port);
417 
418         hostnameStr = virBufferContentAndReset(&buff);
419 
420         if (libssh2_knownhost_addc(sess->knownHosts,
421                                    hostnameStr,
422                                    NULL,
423                                    key,
424                                    keyLength,
425                                    vir_libssh2_key_comment,
426                                    strlen(vir_libssh2_key_comment),
427                                    LIBSSH2_KNOWNHOST_TYPE_PLAIN |
428                                    LIBSSH2_KNOWNHOST_KEYENC_RAW |
429                                    keyType,
430                                    NULL) < 0) {
431             libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
432             virReportError(VIR_ERR_SSH,
433                            _("unable to add SSH host key for host '%s': %s"),
434                            hostnameStr, errmsg);
435             VIR_FREE(hostnameStr);
436             return -1;
437         }
438 
439         VIR_FREE(hostnameStr);
440 
441         /* write the host key file - if applicable */
442         if (sess->knownHostsFile) {
443             if (libssh2_knownhost_writefile(sess->knownHosts,
444                                             sess->knownHostsFile,
445                                          LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) {
446                 libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
447                 virReportError(VIR_ERR_SSH,
448                                _("failed to write known_host file '%s': %s"),
449                                sess->knownHostsFile,
450                                errmsg);
451                 return -1;
452             }
453         }
454         /* key was accepted and added */
455         return 0;
456 
457     case LIBSSH2_KNOWNHOST_CHECK_MATCH:
458         /* host key matches */
459         return 0;
460 
461     case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
462         /* host key verification failed */
463         virReportError(VIR_ERR_AUTH_FAILED,
464                        _("!!! SSH HOST KEY VERIFICATION FAILED !!!: "
465                          "Identity of host '%s:%d' differs from stored identity. "
466                          "Please verify the new host key '%s' to avoid possible "
467                          "man in the middle attack. The key is stored in '%s'."),
468                        sess->hostname, sess->port,
469                        knownHostEntry->key, sess->knownHostsFile);
470         return -1;
471 
472     case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
473         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
474         virReportError(VIR_ERR_SSH,
475                        _("failed to validate SSH host key: %s"),
476                        errmsg);
477         return -1;
478 
479     default: /* should never happen (tm) */
480         virReportError(VIR_ERR_SSH, "%s", _("Unknown error value"));
481         return -1;
482     }
483 
484     return -1;
485 }
486 
487 /* perform ssh agent authentication
488  *
489  * Returns: 0 on success
490  *          1 on authentication failure
491  *         -1 on error
492  */
493 static int
virNetSSHAuthenticateAgent(virNetSSHSession * sess,virNetSSHAuthMethod * priv)494 virNetSSHAuthenticateAgent(virNetSSHSession *sess,
495                            virNetSSHAuthMethod *priv)
496 {
497     struct libssh2_agent_publickey *agent_identity = NULL;
498     bool no_identity = true;
499     int ret;
500     char *errmsg;
501 
502     VIR_DEBUG("sess=%p", sess);
503 
504     if (libssh2_agent_connect(sess->agent) < 0) {
505         virReportError(VIR_ERR_SSH, "%s",
506                        _("Failed to connect to ssh agent"));
507         return 1;
508     }
509 
510     if (libssh2_agent_list_identities(sess->agent) < 0) {
511         virReportError(VIR_ERR_SSH, "%s",
512                        _("Failed to list ssh agent identities"));
513         return 1;
514     }
515 
516     while (!(ret = libssh2_agent_get_identity(sess->agent,
517                                               &agent_identity,
518                                               agent_identity))) {
519         no_identity = false;
520         if (!(ret = libssh2_agent_userauth(sess->agent,
521                                            priv->username,
522                                            agent_identity)))
523             return 0; /* key accepted */
524 
525         VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
526         if (ret != LIBSSH2_ERROR_AUTHENTICATION_FAILED &&
527             ret != LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED &&
528             ret != LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) {
529         VIR_WARNINGS_RESET
530             libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
531             virReportError(VIR_ERR_AUTH_FAILED,
532                            _("failed to authenticate using SSH agent: %s"),
533                            errmsg);
534             return -1;
535         }
536         /* authentication has failed, try next key */
537     }
538 
539     /* if there are no more keys in the agent, the identity retrieval
540      * function returns 1 */
541     if (ret == 1) {
542         if (no_identity) {
543             virReportError(VIR_ERR_AUTH_FAILED, "%s",
544                            _("SSH Agent did not provide any "
545                              "authentication identity"));
546         } else {
547             virReportError(VIR_ERR_AUTH_FAILED, "%s",
548                            _("All identities provided by the SSH Agent "
549                              "were rejected"));
550         }
551         return 1;
552     }
553 
554     libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
555     virReportError(VIR_ERR_AUTH_FAILED,
556                    _("failed to authenticate using SSH agent: %s"),
557                    errmsg);
558     return -1;
559 }
560 
561 /* perform private key authentication
562  *
563  * Returns: 0 on success
564  *          1 on authentication failure
565  *         -1 on error
566  */
567 static int
virNetSSHAuthenticatePrivkey(virNetSSHSession * sess,virNetSSHAuthMethod * priv)568 virNetSSHAuthenticatePrivkey(virNetSSHSession *sess,
569                              virNetSSHAuthMethod *priv)
570 {
571     virConnectCredential retr_passphrase;
572     size_t i;
573     char *errmsg;
574     int ret;
575     char *tmp;
576 
577     VIR_DEBUG("sess=%p", sess);
578 
579     /* try open the key with no password */
580     if ((ret = libssh2_userauth_publickey_fromfile(sess->session,
581                                                    priv->username,
582                                                    NULL,
583                                                    priv->filename,
584                                                    priv->password)) == 0)
585         return 0; /* success */
586 
587     VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
588     if (priv->password ||
589         ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED ||
590         ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) {
591     VIR_WARNINGS_RESET
592         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
593         virReportError(VIR_ERR_AUTH_FAILED,
594                        _("authentication with private key '%s' "
595                          "has failed: %s"),
596                        priv->filename, errmsg);
597         return 1; /* auth failed */
598     }
599 
600     /* request user's key password */
601     if (!sess->cred || !sess->cred->cb) {
602         virReportError(VIR_ERR_SSH, "%s",
603                        _("No user interaction callback provided: "
604                          "Can't retrieve private key passphrase"));
605         return -1;
606     }
607 
608     memset(&retr_passphrase, 0, sizeof(virConnectCredential));
609     retr_passphrase.type = -1;
610 
611     for (i = 0; i < sess->cred->ncredtype; i++) {
612         if (sess->cred->credtype[i] == VIR_CRED_PASSPHRASE ||
613             sess->cred->credtype[i] == VIR_CRED_NOECHOPROMPT) {
614             retr_passphrase.type = sess->cred->credtype[i];
615             break;
616         }
617     }
618 
619     if (retr_passphrase.type < 0) {
620         virReportError(VIR_ERR_SSH, "%s",
621                        _("no suitable method to retrieve key passphrase"));
622         return -1;
623     }
624 
625     retr_passphrase.prompt = g_strdup_printf(_("Passphrase for key '%s'"),
626                                              priv->filename);
627 
628     if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) {
629         virReportError(VIR_ERR_SSH, "%s",
630                        _("failed to retrieve private key passphrase: "
631                          "callback has failed"));
632         tmp = (char *)retr_passphrase.prompt;
633         VIR_FREE(tmp);
634         return -1;
635     }
636 
637     tmp = (char *)retr_passphrase.prompt;
638     VIR_FREE(tmp);
639 
640     ret = libssh2_userauth_publickey_fromfile(sess->session,
641                                               priv->username,
642                                               NULL,
643                                               priv->filename,
644                                               retr_passphrase.result);
645 
646     VIR_FREE(retr_passphrase.result);
647 
648     if (ret < 0) {
649         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
650         virReportError(VIR_ERR_AUTH_FAILED,
651                        _("authentication with private key '%s' "
652                          "has failed: %s"),
653                        priv->filename, errmsg);
654 
655         VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
656         if (ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED ||
657             ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
658             return 1;
659         else
660             return -1;
661         VIR_WARNINGS_RESET
662     }
663 
664     return 0;
665 }
666 
667 
668 /* perform password authentication, either directly or request the password
669  *
670  * Returns: 0 on success
671  *          1 on authentication failure
672  *         -1 on error
673  */
674 static int
virNetSSHAuthenticatePassword(virNetSSHSession * sess,virNetSSHAuthMethod * priv)675 virNetSSHAuthenticatePassword(virNetSSHSession *sess,
676                               virNetSSHAuthMethod *priv)
677 {
678     char *password = NULL;
679     char *errmsg;
680     int ret = -1;
681     int rc;
682 
683     VIR_DEBUG("sess=%p", sess);
684 
685     if (priv->password) {
686         /* tunnelled password authentication */
687         if ((rc = libssh2_userauth_password(sess->session,
688                                             priv->username,
689                                             priv->password)) == 0) {
690             ret = 0;
691             goto cleanup;
692         }
693     } else {
694         /* password authentication with interactive password request */
695         if (!sess->cred || !sess->cred->cb) {
696             virReportError(VIR_ERR_SSH, "%s",
697                            _("Can't perform authentication: "
698                              "Authentication callback not provided"));
699             goto cleanup;
700         }
701 
702         /* Try the authenticating the set amount of times. The server breaks the
703          * connection if maximum number of bad auth tries is exceeded */
704         while (true) {
705             if (!(password = virAuthGetPasswordPath(sess->authPath, sess->cred,
706                                                     "ssh", priv->username,
707                                                     sess->hostname)))
708                 goto cleanup;
709 
710             /* tunnelled password authentication */
711             if ((rc = libssh2_userauth_password(sess->session,
712                                                 priv->username,
713                                                 password)) == 0) {
714                 ret = 0;
715                 goto cleanup;
716             }
717 
718             if (rc != LIBSSH2_ERROR_AUTHENTICATION_FAILED)
719                 break;
720 
721             VIR_FREE(password);
722         }
723     }
724 
725     /* error path */
726     libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
727     virReportError(VIR_ERR_AUTH_FAILED,
728                    _("authentication failed: %s"), errmsg);
729 
730     /* determine exist status */
731     if (rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
732         ret = 1;
733     else
734         ret = -1;
735 
736  cleanup:
737     VIR_FREE(password);
738     return ret;
739 }
740 
741 
742 /* perform keyboard interactive authentication
743  *
744  * Returns: 0 on success
745  *          1 on authentication failure
746  *         -1 on error
747  */
748 static int
virNetSSHAuthenticateKeyboardInteractive(virNetSSHSession * sess,virNetSSHAuthMethod * priv)749 virNetSSHAuthenticateKeyboardInteractive(virNetSSHSession *sess,
750                                          virNetSSHAuthMethod *priv)
751 {
752     char *errmsg;
753     int ret;
754 
755     VIR_DEBUG("sess=%p", sess);
756 
757     if (!sess->cred || !sess->cred->cb) {
758         virReportError(VIR_ERR_SSH, "%s",
759                        _("Can't perform keyboard-interactive authentication: "
760                          "Authentication callback not provided "));
761         return -1;
762     }
763 
764     /* Try the authenticating the set amount of times. The server breaks the
765      * connection if maximum number of bad auth tries is exceeded */
766     while (priv->tries < 0 || priv->tries-- > 0) {
767         ret = libssh2_userauth_keyboard_interactive(sess->session,
768                                                     priv->username,
769                                                     virNetSSHKbIntCb);
770 
771         /* check for errors while calling the callback */
772         switch (sess->authCbErr) {
773         case VIR_NET_SSH_AUTHCB_NO_METHOD:
774             virReportError(VIR_ERR_SSH, "%s",
775                            _("no suitable method to retrieve "
776                              "authentication credentials"));
777             return -1;
778         case VIR_NET_SSH_AUTHCB_OOM:
779             /* OOM error already reported */
780             return -1;
781         case VIR_NET_SSH_AUTHCB_RETR_ERR:
782             virReportError(VIR_ERR_SSH, "%s",
783                            _("failed to retrieve credentials"));
784             return -1;
785         case VIR_NET_SSH_AUTHCB_OK:
786             /* everything went fine, let's continue */
787             break;
788         }
789 
790         if (ret == 0)
791             /* authentication succeeded */
792             return 0;
793 
794         if (ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
795             continue; /* try again */
796 
797         if (ret < 0) {
798             libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
799             virReportError(VIR_ERR_AUTH_FAILED,
800                            _("keyboard interactive authentication failed: %s"),
801                            errmsg);
802             return -1;
803         }
804     }
805     libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
806     virReportError(VIR_ERR_AUTH_FAILED,
807                    _("keyboard interactive authentication failed: %s"),
808                    errmsg);
809     return 1;
810 }
811 
812 /* select auth method and authenticate */
813 static int
virNetSSHAuthenticate(virNetSSHSession * sess)814 virNetSSHAuthenticate(virNetSSHSession *sess)
815 {
816     virNetSSHAuthMethod *auth;
817     bool no_method = false;
818     bool auth_failed = false;
819     char *auth_list;
820     char *errmsg;
821     size_t i;
822     int ret;
823 
824     VIR_DEBUG("sess=%p", sess);
825 
826     if (!sess->nauths) {
827         virReportError(VIR_ERR_SSH, "%s",
828                        _("No authentication methods and credentials "
829                          "provided"));
830         return -1;
831     }
832 
833     /* obtain list of supported auth methods */
834     auth_list = libssh2_userauth_list(sess->session,
835                                       sess->auths[0]->username,
836                                       strlen(sess->auths[0]->username));
837     if (!auth_list) {
838         /* unlikely event, authentication succeeded with NONE as method */
839         if (libssh2_userauth_authenticated(sess->session) == 1)
840             return 0;
841 
842         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
843         virReportError(VIR_ERR_SSH,
844                        _("couldn't retrieve authentication methods list: %s"),
845                        errmsg);
846         return -1;
847     }
848 
849     for (i = 0; i < sess->nauths; i++) {
850         auth = sess->auths[i];
851 
852         ret = 2;
853         virResetLastError();
854 
855         switch (auth->method) {
856         case VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE:
857             if (strstr(auth_list, "keyboard-interactive"))
858                 ret = virNetSSHAuthenticateKeyboardInteractive(sess, auth);
859             break;
860         case VIR_NET_SSH_AUTH_AGENT:
861             if (strstr(auth_list, "publickey"))
862                 ret = virNetSSHAuthenticateAgent(sess, auth);
863             break;
864         case VIR_NET_SSH_AUTH_PRIVKEY:
865             if (strstr(auth_list, "publickey"))
866                 ret = virNetSSHAuthenticatePrivkey(sess, auth);
867             break;
868         case VIR_NET_SSH_AUTH_PASSWORD:
869             if (strstr(auth_list, "password"))
870                 ret = virNetSSHAuthenticatePassword(sess, auth);
871             break;
872         }
873 
874         /* return on success or error */
875         if (ret <= 0)
876             return ret;
877 
878         /* the authentication method is not supported */
879         if (ret == 2)
880             no_method = true;
881 
882         /* authentication with this method has failed */
883         if (ret == 1)
884             auth_failed = true;
885     }
886 
887     if (sess->nauths == 0) {
888         virReportError(VIR_ERR_AUTH_FAILED, "%s",
889                        _("No authentication methods supplied"));
890     } else if (sess->nauths == 1) {
891         /* pass through the error */
892     } else if (no_method && !auth_failed) {
893         virReportError(VIR_ERR_AUTH_FAILED, "%s",
894                        _("None of the requested authentication methods "
895                          "are supported by the server"));
896     } else {
897         virReportError(VIR_ERR_AUTH_FAILED, "%s",
898                        _("All provided authentication methods with credentials "
899                          "were rejected by the server"));
900     }
901 
902     return -1;
903 }
904 
905 /* open channel */
906 static int
virNetSSHOpenChannel(virNetSSHSession * sess)907 virNetSSHOpenChannel(virNetSSHSession *sess)
908 {
909     char *errmsg;
910 
911     sess->channel = libssh2_channel_open_session(sess->session);
912     if (!sess->channel) {
913         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
914         virReportError(VIR_ERR_SSH,
915                        _("failed to open ssh channel: %s"),
916                        errmsg);
917         return -1;
918     }
919 
920     if (libssh2_channel_exec(sess->channel, sess->channelCommand) != 0) {
921         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
922         virReportError(VIR_ERR_SSH,
923                        _("failed to execute command '%s': %s"),
924                        sess->channelCommand,
925                        errmsg);
926         return -1;
927     }
928 
929     /* nonblocking mode - currently does nothing */
930     libssh2_channel_set_blocking(sess->channel, 0);
931 
932     /* channel open */
933     return 0;
934 }
935 
936 /* validate if all required parameters are configured */
937 static int
virNetSSHValidateConfig(virNetSSHSession * sess)938 virNetSSHValidateConfig(virNetSSHSession *sess)
939 {
940     if (sess->nauths == 0) {
941         virReportError(VIR_ERR_SSH, "%s",
942                        _("No authentication methods and credentials "
943                          "provided"));
944         return -1;
945     }
946 
947     if (!sess->channelCommand) {
948         virReportError(VIR_ERR_SSH, "%s",
949                        _("No channel command provided"));
950         return -1;
951     }
952 
953     if (sess->hostKeyVerify != VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE) {
954         if (!sess->hostname) {
955             virReportError(VIR_ERR_SSH, "%s",
956                            _("Hostname is needed for host key verification"));
957             return -1;
958         }
959     }
960 
961     /* everything ok */
962     return 0;
963 }
964 
965 /* ### PUBLIC API ### */
966 int
virNetSSHSessionAuthSetCallback(virNetSSHSession * sess,virConnectAuthPtr auth)967 virNetSSHSessionAuthSetCallback(virNetSSHSession *sess,
968                                 virConnectAuthPtr auth)
969 {
970     virObjectLock(sess);
971     sess->cred = auth;
972     virObjectUnlock(sess);
973     return 0;
974 }
975 
976 void
virNetSSHSessionAuthReset(virNetSSHSession * sess)977 virNetSSHSessionAuthReset(virNetSSHSession *sess)
978 {
979     virObjectLock(sess);
980     virNetSSHSessionAuthMethodsClear(sess);
981     virObjectUnlock(sess);
982 }
983 
984 int
virNetSSHSessionAuthAddPasswordAuth(virNetSSHSession * sess,virURI * uri,const char * username)985 virNetSSHSessionAuthAddPasswordAuth(virNetSSHSession *sess,
986                                     virURI *uri,
987                                     const char *username)
988 {
989     virNetSSHAuthMethod *auth;
990     char *user = NULL;
991 
992     if (uri) {
993         VIR_FREE(sess->authPath);
994 
995         if (virAuthGetConfigFilePathURI(uri, &sess->authPath) < 0)
996             goto error;
997     }
998 
999     if (!username) {
1000         if (!(user = virAuthGetUsernamePath(sess->authPath, sess->cred,
1001                                             "ssh", NULL, sess->hostname)))
1002             goto error;
1003     } else {
1004         user = g_strdup(username);
1005     }
1006 
1007     virObjectLock(sess);
1008 
1009     if (!(auth = virNetSSHSessionAuthMethodNew(sess)))
1010         goto error;
1011 
1012     auth->username = user;
1013     auth->method = VIR_NET_SSH_AUTH_PASSWORD;
1014 
1015     virObjectUnlock(sess);
1016     return 0;
1017 
1018  error:
1019     VIR_FREE(user);
1020     virObjectUnlock(sess);
1021     return -1;
1022 }
1023 
1024 int
virNetSSHSessionAuthAddAgentAuth(virNetSSHSession * sess,const char * username)1025 virNetSSHSessionAuthAddAgentAuth(virNetSSHSession *sess,
1026                                  const char *username)
1027 {
1028     virNetSSHAuthMethod *auth;
1029     char *user = NULL;
1030 
1031     if (!username) {
1032         virReportError(VIR_ERR_SSH, "%s",
1033                        _("Username must be provided "
1034                          "for ssh agent authentication"));
1035         return -1;
1036     }
1037 
1038     virObjectLock(sess);
1039 
1040     user = g_strdup(username);
1041 
1042     if (!(auth = virNetSSHSessionAuthMethodNew(sess)))
1043         goto error;
1044 
1045     auth->username = user;
1046     auth->method = VIR_NET_SSH_AUTH_AGENT;
1047 
1048     virObjectUnlock(sess);
1049     return 0;
1050 
1051  error:
1052     VIR_FREE(user);
1053     virObjectUnlock(sess);
1054     return -1;
1055 }
1056 
1057 int
virNetSSHSessionAuthAddPrivKeyAuth(virNetSSHSession * sess,const char * username,const char * keyfile,const char * password)1058 virNetSSHSessionAuthAddPrivKeyAuth(virNetSSHSession *sess,
1059                                    const char *username,
1060                                    const char *keyfile,
1061                                    const char *password)
1062 {
1063     virNetSSHAuthMethod *auth;
1064 
1065     char *user = NULL;
1066     char *pass = NULL;
1067     char *file = NULL;
1068 
1069     if (!username || !keyfile) {
1070         virReportError(VIR_ERR_SSH, "%s",
1071                        _("Username and key file path must be provided "
1072                          "for private key authentication"));
1073         return -1;
1074     }
1075 
1076     virObjectLock(sess);
1077 
1078     user = g_strdup(username);
1079     file = g_strdup(keyfile);
1080     pass = g_strdup(password);
1081 
1082     if (!(auth = virNetSSHSessionAuthMethodNew(sess)))
1083         goto error;
1084 
1085     auth->username = user;
1086     auth->password = pass;
1087     auth->filename = file;
1088     auth->method = VIR_NET_SSH_AUTH_PRIVKEY;
1089 
1090     virObjectUnlock(sess);
1091     return 0;
1092 
1093  error:
1094     VIR_FREE(user);
1095     VIR_FREE(pass);
1096     VIR_FREE(file);
1097     virObjectUnlock(sess);
1098     return -1;
1099 }
1100 
1101 int
virNetSSHSessionAuthAddKeyboardAuth(virNetSSHSession * sess,const char * username,int tries)1102 virNetSSHSessionAuthAddKeyboardAuth(virNetSSHSession *sess,
1103                                     const char *username,
1104                                     int tries)
1105 {
1106     virNetSSHAuthMethod *auth;
1107     char *user = NULL;
1108 
1109     if (!username) {
1110         virReportError(VIR_ERR_SSH, "%s",
1111                        _("Username must be provided "
1112                          "for ssh agent authentication"));
1113         return -1;
1114     }
1115 
1116     virObjectLock(sess);
1117 
1118     user = g_strdup(username);
1119 
1120     if (!(auth = virNetSSHSessionAuthMethodNew(sess)))
1121         goto error;
1122 
1123     auth->username = user;
1124     auth->tries = tries;
1125     auth->method = VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE;
1126 
1127     virObjectUnlock(sess);
1128     return 0;
1129 
1130  error:
1131     VIR_FREE(user);
1132     virObjectUnlock(sess);
1133     return -1;
1134 
1135 }
1136 
1137 void
virNetSSHSessionSetChannelCommand(virNetSSHSession * sess,const char * command)1138 virNetSSHSessionSetChannelCommand(virNetSSHSession *sess,
1139                                   const char *command)
1140 {
1141     virObjectLock(sess);
1142 
1143     VIR_FREE(sess->channelCommand);
1144 
1145     sess->channelCommand = g_strdup(command);
1146 
1147     virObjectUnlock(sess);
1148 }
1149 
1150 int
virNetSSHSessionSetHostKeyVerification(virNetSSHSession * sess,const char * hostname,int port,const char * hostsfile,virNetSSHHostkeyVerify opt,unsigned int flags)1151 virNetSSHSessionSetHostKeyVerification(virNetSSHSession *sess,
1152                                        const char *hostname,
1153                                        int port,
1154                                        const char *hostsfile,
1155                                        virNetSSHHostkeyVerify opt,
1156                                        unsigned int flags)
1157 {
1158     char *errmsg;
1159 
1160     virObjectLock(sess);
1161 
1162     sess->port = port;
1163     sess->hostKeyVerify = opt;
1164 
1165     VIR_FREE(sess->hostname);
1166 
1167     sess->hostname = g_strdup(hostname);
1168 
1169     /* load the known hosts file */
1170     if (hostsfile) {
1171         if (virFileExists(hostsfile)) {
1172             if (libssh2_knownhost_readfile(sess->knownHosts,
1173                                            hostsfile,
1174                                            LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) {
1175                 libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
1176                 virReportError(VIR_ERR_SSH,
1177                                _("unable to load knownhosts file '%s': %s"),
1178                                hostsfile, errmsg);
1179                 goto error;
1180             }
1181         } else if (!(flags & VIR_NET_SSH_HOSTKEY_FILE_CREATE)) {
1182             virReportError(VIR_ERR_SSH,
1183                            _("known hosts file '%s' does not exist"),
1184                            hostsfile);
1185             goto error;
1186         }
1187 
1188         /* set filename only if writing to the known hosts file is requested */
1189         if (!(flags & VIR_NET_SSH_HOSTKEY_FILE_READONLY)) {
1190             VIR_FREE(sess->knownHostsFile);
1191             sess->knownHostsFile = g_strdup(hostsfile);
1192         }
1193     }
1194 
1195     virObjectUnlock(sess);
1196     return 0;
1197 
1198  error:
1199     virObjectUnlock(sess);
1200     return -1;
1201 }
1202 
1203 /* allocate and initialize a ssh session object */
virNetSSHSessionNew(void)1204 virNetSSHSession *virNetSSHSessionNew(void)
1205 {
1206     virNetSSHSession *sess = NULL;
1207 
1208     if (virNetSSHSessionInitialize() < 0)
1209         goto error;
1210 
1211     if (!(sess = virObjectLockableNew(virNetSSHSessionClass)))
1212         goto error;
1213 
1214     /* initialize session data, use the internal data for callbacks
1215      * and stick to default memory management functions */
1216     if (!(sess->session = libssh2_session_init_ex(NULL,
1217                                                   NULL,
1218                                                   NULL,
1219                                                   (void *)sess))) {
1220         virReportError(VIR_ERR_SSH, "%s",
1221                        _("Failed to initialize libssh2 session"));
1222         goto error;
1223     }
1224 
1225     if (!(sess->knownHosts = libssh2_knownhost_init(sess->session))) {
1226         virReportError(VIR_ERR_SSH, "%s",
1227                        _("Failed to initialize libssh2 known hosts table"));
1228         goto error;
1229     }
1230 
1231     if (!(sess->agent = libssh2_agent_init(sess->session))) {
1232         virReportError(VIR_ERR_SSH, "%s",
1233                        _("Failed to initialize libssh2 agent handle"));
1234         goto error;
1235     }
1236 
1237     VIR_DEBUG("virNetSSHSession *: %p, LIBSSH2_SESSION: %p",
1238               sess, sess->session);
1239 
1240     /* set blocking mode for libssh2 until handshake is complete */
1241     libssh2_session_set_blocking(sess->session, 1);
1242 
1243     /* default states for config variables */
1244     sess->state = VIR_NET_SSH_STATE_NEW;
1245     sess->hostKeyVerify = VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE;
1246 
1247     return sess;
1248 
1249  error:
1250     virObjectUnref(sess);
1251     return NULL;
1252 }
1253 
1254 int
virNetSSHSessionConnect(virNetSSHSession * sess,int sock)1255 virNetSSHSessionConnect(virNetSSHSession *sess,
1256                         int sock)
1257 {
1258     int ret;
1259     char *errmsg;
1260 
1261     VIR_DEBUG("sess=%p, sock=%d", sess, sock);
1262 
1263     if (!sess || sess->state != VIR_NET_SSH_STATE_NEW) {
1264         virReportError(VIR_ERR_SSH, "%s",
1265                        _("Invalid virNetSSHSession *"));
1266         return -1;
1267     }
1268 
1269     virObjectLock(sess);
1270 
1271     /* check if configuration is valid */
1272     if ((ret = virNetSSHValidateConfig(sess)) < 0)
1273         goto error;
1274 
1275     /* open session */
1276     ret = libssh2_session_handshake(sess->session, sock);
1277     /* libssh2 is in blocking mode, so EAGAIN will never happen */
1278     if (ret < 0) {
1279         libssh2_session_last_error(sess->session, &errmsg, NULL, 0);
1280         virReportError(VIR_ERR_NO_CONNECT,
1281                        _("SSH session handshake failed: %s"),
1282                        errmsg);
1283         goto error;
1284     }
1285 
1286     /* verify the SSH host key */
1287     if ((ret = virNetSSHCheckHostKey(sess)) != 0)
1288         goto error;
1289 
1290     /* authenticate */
1291     if ((ret = virNetSSHAuthenticate(sess)) != 0)
1292         goto error;
1293 
1294     /* open channel */
1295     if ((ret = virNetSSHOpenChannel(sess)) != 0)
1296         goto error;
1297 
1298     /* all set */
1299     /* switch to nonblocking mode and return */
1300     libssh2_session_set_blocking(sess->session, 0);
1301     sess->state = VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE;
1302 
1303     virObjectUnlock(sess);
1304     return ret;
1305 
1306  error:
1307     sess->state = VIR_NET_SSH_STATE_ERROR;
1308     virObjectUnlock(sess);
1309     return ret;
1310 }
1311 
1312 /* do a read from a ssh channel, used instead of normal read on socket */
1313 ssize_t
virNetSSHChannelRead(virNetSSHSession * sess,char * buf,size_t len)1314 virNetSSHChannelRead(virNetSSHSession *sess,
1315                      char *buf,
1316                      size_t len)
1317 {
1318     ssize_t ret = -1;
1319     ssize_t read_n = 0;
1320 
1321     virObjectLock(sess);
1322 
1323     if (sess->state != VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE) {
1324         if (sess->state == VIR_NET_SSH_STATE_ERROR_REMOTE)
1325             virReportError(VIR_ERR_SSH,
1326                            _("Remote program terminated "
1327                              "with non-zero code: %d"),
1328                            sess->channelCommandReturnValue);
1329         else
1330             virReportError(VIR_ERR_SSH, "%s",
1331                            _("Tried to write socket in error state"));
1332 
1333         virObjectUnlock(sess);
1334         return -1;
1335     }
1336 
1337     if (sess->bufUsed > 0) {
1338         /* copy the rest (or complete) internal buffer to the output buffer */
1339         memcpy(buf,
1340                sess->rbuf + sess->bufStart,
1341                len > sess->bufUsed ? sess->bufUsed : len);
1342 
1343         if (len >= sess->bufUsed) {
1344             read_n = sess->bufUsed;
1345 
1346             sess->bufStart = 0;
1347             sess->bufUsed = 0;
1348         } else {
1349             read_n = len;
1350             sess->bufUsed -= len;
1351             sess->bufStart += len;
1352 
1353             goto success;
1354         }
1355     }
1356 
1357     /* continue reading into the buffer supplied */
1358     if (read_n < len) {
1359         ret = libssh2_channel_read(sess->channel,
1360                                    buf + read_n,
1361                                    len - read_n);
1362 
1363         if (ret == LIBSSH2_ERROR_EAGAIN)
1364             goto success;
1365 
1366         if (ret < 0)
1367             goto error;
1368 
1369         read_n += ret;
1370     }
1371 
1372     /* try to read something into the internal buffer */
1373     if (sess->bufUsed == 0) {
1374         ret = libssh2_channel_read(sess->channel,
1375                                    sess->rbuf,
1376                                    VIR_NET_SSH_BUFFER_SIZE);
1377 
1378         if (ret == LIBSSH2_ERROR_EAGAIN)
1379             goto success;
1380 
1381         if (ret < 0)
1382             goto error;
1383 
1384         sess->bufUsed = ret;
1385         sess->bufStart = 0;
1386     }
1387 
1388     if (read_n == 0) {
1389         /* get rid of data in stderr stream */
1390         ret = libssh2_channel_read_stderr(sess->channel,
1391                                           sess->rbuf,
1392                                           VIR_NET_SSH_BUFFER_SIZE - 1);
1393         if (ret > 0) {
1394             sess->rbuf[ret] = '\0';
1395             VIR_DEBUG("flushing stderr, data='%s'",  sess->rbuf);
1396         }
1397     }
1398 
1399     if (libssh2_channel_eof(sess->channel)) {
1400         if (libssh2_channel_get_exit_status(sess->channel)) {
1401             virReportError(VIR_ERR_SSH,
1402                            _("Remote command terminated with non-zero code: %d"),
1403                            libssh2_channel_get_exit_status(sess->channel));
1404             sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel);
1405             sess->state = VIR_NET_SSH_STATE_ERROR_REMOTE;
1406             virObjectUnlock(sess);
1407             return -1;
1408         }
1409 
1410         sess->state = VIR_NET_SSH_STATE_CLOSED;
1411         virObjectUnlock(sess);
1412         return -1;
1413     }
1414 
1415  success:
1416     virObjectUnlock(sess);
1417     return read_n;
1418 
1419  error:
1420     sess->state = VIR_NET_SSH_STATE_ERROR;
1421     virObjectUnlock(sess);
1422     return ret;
1423 }
1424 
1425 ssize_t
virNetSSHChannelWrite(virNetSSHSession * sess,const char * buf,size_t len)1426 virNetSSHChannelWrite(virNetSSHSession *sess,
1427                       const char *buf,
1428                       size_t len)
1429 {
1430     ssize_t ret;
1431 
1432     virObjectLock(sess);
1433 
1434     if (sess->state != VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE) {
1435         if (sess->state == VIR_NET_SSH_STATE_ERROR_REMOTE)
1436             virReportError(VIR_ERR_SSH,
1437                            _("Remote program terminated with non-zero code: %d"),
1438                            sess->channelCommandReturnValue);
1439         else
1440             virReportError(VIR_ERR_SSH, "%s",
1441                            _("Tried to write socket in error state"));
1442         ret = -1;
1443         goto cleanup;
1444     }
1445 
1446     if (libssh2_channel_eof(sess->channel)) {
1447         if (libssh2_channel_get_exit_status(sess->channel)) {
1448             virReportError(VIR_ERR_SSH,
1449                            _("Remote program terminated with non-zero code: %d"),
1450                            libssh2_channel_get_exit_status(sess->channel));
1451             sess->state = VIR_NET_SSH_STATE_ERROR_REMOTE;
1452             sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel);
1453 
1454             ret = -1;
1455             goto cleanup;
1456         }
1457 
1458         sess->state = VIR_NET_SSH_STATE_CLOSED;
1459         ret = -1;
1460         goto cleanup;
1461     }
1462 
1463     ret = libssh2_channel_write(sess->channel, buf, len);
1464     if (ret == LIBSSH2_ERROR_EAGAIN) {
1465         ret = 0;
1466         goto cleanup;
1467     }
1468 
1469     if (ret < 0) {
1470         char *msg;
1471         sess->state = VIR_NET_SSH_STATE_ERROR;
1472         libssh2_session_last_error(sess->session, &msg, NULL, 0);
1473         virReportError(VIR_ERR_SSH,
1474                        _("write failed: %s"), msg);
1475     }
1476 
1477  cleanup:
1478     virObjectUnlock(sess);
1479     return ret;
1480 }
1481 
1482 bool
virNetSSHSessionHasCachedData(virNetSSHSession * sess)1483 virNetSSHSessionHasCachedData(virNetSSHSession *sess)
1484 {
1485     bool ret;
1486 
1487     if (!sess)
1488         return false;
1489 
1490     virObjectLock(sess);
1491 
1492     ret = sess->bufUsed > 0;
1493 
1494     virObjectUnlock(sess);
1495     return ret;
1496 }
1497