mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 02:41:14 +00:00
f951277716
Now that the virAuthGet*Path API's generate all the error messages we can remove them from the callers. Signed-off-by: John Ferlan <jferlan@redhat.com> Reviewed-by: Marcos Paulo de Souza <marcos.souza.org@gmail.com>
1495 lines
42 KiB
C
1495 lines
42 KiB
C
/*
|
|
* virnetlibsshsession.c: ssh network transport provider based on libssh
|
|
*
|
|
* Copyright (C) 2012-2016 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Peter Krempa <pkrempa@redhat.com>
|
|
* Author: Pino Toscano <ptoscano@redhat.com>
|
|
*/
|
|
#include <config.h>
|
|
#include <libssh/libssh.h>
|
|
|
|
#include "virnetlibsshsession.h"
|
|
|
|
#include "internal.h"
|
|
#include "viralloc.h"
|
|
#include "virlog.h"
|
|
#include "configmake.h"
|
|
#include "virutil.h"
|
|
#include "virerror.h"
|
|
#include "virobject.h"
|
|
#include "virstring.h"
|
|
#include "virauth.h"
|
|
#include "virbuffer.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_LIBSSH
|
|
|
|
VIR_LOG_INIT("rpc.netlibsshsession");
|
|
|
|
#define VIR_NET_LIBSSH_BUFFER_SIZE 1024
|
|
|
|
/* TRACE_LIBSSH=<level> enables tracing in libssh itself.
|
|
* The meaning of <level> is described here:
|
|
* http://api.libssh.org/master/group__libssh__log.html
|
|
*
|
|
* The LIBVIRT_LIBSSH_DEBUG environment variable can be used
|
|
* to set/override the level of libssh debug.
|
|
*/
|
|
#define TRACE_LIBSSH 0
|
|
|
|
typedef enum {
|
|
VIR_NET_LIBSSH_STATE_NEW,
|
|
VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE,
|
|
VIR_NET_LIBSSH_STATE_CLOSED,
|
|
VIR_NET_LIBSSH_STATE_ERROR,
|
|
VIR_NET_LIBSSH_STATE_ERROR_REMOTE,
|
|
} virNetLibsshSessionState;
|
|
|
|
typedef enum {
|
|
VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE,
|
|
VIR_NET_LIBSSH_AUTH_PASSWORD,
|
|
VIR_NET_LIBSSH_AUTH_PRIVKEY,
|
|
VIR_NET_LIBSSH_AUTH_AGENT
|
|
} virNetLibsshAuthMethods;
|
|
|
|
|
|
typedef struct _virNetLibsshAuthMethod virNetLibsshAuthMethod;
|
|
typedef virNetLibsshAuthMethod *virNetLibsshAuthMethodPtr;
|
|
|
|
struct _virNetLibsshAuthMethod {
|
|
virNetLibsshAuthMethods method;
|
|
int ssh_flags; /* SSH_AUTH_METHOD_* for this auth method */
|
|
|
|
char *password;
|
|
char *filename;
|
|
|
|
int tries;
|
|
};
|
|
|
|
struct _virNetLibsshSession {
|
|
virObjectLockable parent;
|
|
virNetLibsshSessionState state;
|
|
|
|
/* libssh internal stuff */
|
|
ssh_session session;
|
|
ssh_channel channel;
|
|
|
|
/* for host key checking */
|
|
virNetLibsshHostkeyVerify hostKeyVerify;
|
|
char *knownHostsFile;
|
|
char *hostname;
|
|
int port;
|
|
|
|
/* authentication stuff */
|
|
char *username;
|
|
virConnectAuthPtr cred;
|
|
char *authPath;
|
|
size_t nauths;
|
|
virNetLibsshAuthMethodPtr *auths;
|
|
|
|
/* channel stuff */
|
|
char *channelCommand;
|
|
int channelCommandReturnValue;
|
|
|
|
/* read cache */
|
|
char rbuf[VIR_NET_LIBSSH_BUFFER_SIZE];
|
|
size_t bufUsed;
|
|
size_t bufStart;
|
|
};
|
|
|
|
static void
|
|
virNetLibsshSessionAuthMethodsFree(virNetLibsshSessionPtr sess)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < sess->nauths; i++) {
|
|
VIR_DISPOSE_STRING(sess->auths[i]->password);
|
|
VIR_FREE(sess->auths[i]->filename);
|
|
VIR_FREE(sess->auths[i]);
|
|
}
|
|
|
|
VIR_FREE(sess->auths);
|
|
sess->nauths = 0;
|
|
}
|
|
|
|
static void
|
|
virNetLibsshSessionDispose(void *obj)
|
|
{
|
|
virNetLibsshSessionPtr sess = obj;
|
|
VIR_DEBUG("sess=0x%p", sess);
|
|
|
|
if (!sess)
|
|
return;
|
|
|
|
if (sess->channel) {
|
|
ssh_channel_send_eof(sess->channel);
|
|
ssh_channel_close(sess->channel);
|
|
ssh_channel_free(sess->channel);
|
|
}
|
|
|
|
if (sess->session) {
|
|
ssh_disconnect(sess->session);
|
|
ssh_free(sess->session);
|
|
}
|
|
|
|
virNetLibsshSessionAuthMethodsFree(sess);
|
|
|
|
VIR_FREE(sess->channelCommand);
|
|
VIR_FREE(sess->hostname);
|
|
VIR_FREE(sess->knownHostsFile);
|
|
VIR_FREE(sess->authPath);
|
|
VIR_FREE(sess->username);
|
|
}
|
|
|
|
static virClassPtr virNetLibsshSessionClass;
|
|
static int
|
|
virNetLibsshSessionOnceInit(void)
|
|
{
|
|
const char *dbgLevelStr;
|
|
|
|
if (!VIR_CLASS_NEW(virNetLibsshSession, virClassForObjectLockable()))
|
|
return -1;
|
|
|
|
if (ssh_init() < 0) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to initialize libssh"));
|
|
return -1;
|
|
}
|
|
|
|
#if TRACE_LIBSSH != 0
|
|
ssh_set_log_level(TRACE_LIBSSH);
|
|
#endif
|
|
|
|
dbgLevelStr = virGetEnvAllowSUID("LIBVIRT_LIBSSH_DEBUG");
|
|
if (dbgLevelStr) {
|
|
int dbgLevel = virParseNumber(&dbgLevelStr);
|
|
ssh_set_log_level(dbgLevel);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
VIR_ONCE_GLOBAL_INIT(virNetLibsshSession);
|
|
|
|
static virNetLibsshAuthMethodPtr
|
|
virNetLibsshSessionAuthMethodNew(virNetLibsshSessionPtr sess)
|
|
{
|
|
virNetLibsshAuthMethodPtr auth;
|
|
|
|
if (VIR_ALLOC(auth) < 0)
|
|
goto error;
|
|
|
|
if (VIR_EXPAND_N(sess->auths, sess->nauths, 1) < 0)
|
|
goto error;
|
|
|
|
sess->auths[sess->nauths - 1] = auth;
|
|
|
|
return auth;
|
|
|
|
error:
|
|
VIR_FREE(auth);
|
|
return NULL;
|
|
}
|
|
|
|
/* string representation of public key of remote server */
|
|
static char *
|
|
virLibsshServerKeyAsString(virNetLibsshSessionPtr sess)
|
|
{
|
|
int ret;
|
|
ssh_key key;
|
|
unsigned char *keyhash;
|
|
size_t keyhashlen;
|
|
char *str;
|
|
|
|
if (ssh_get_server_publickey(sess->session, &key) != SSH_OK) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to get the key of the current "
|
|
"session"));
|
|
return NULL;
|
|
}
|
|
|
|
/* calculate remote key hash, using SHA1 algorithm that is
|
|
* usual in OpenSSH. The returned value must be freed */
|
|
ret = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA1,
|
|
&keyhash, &keyhashlen);
|
|
ssh_key_free(key);
|
|
if (ret < 0) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to calculate ssh host key hash"));
|
|
return NULL;
|
|
}
|
|
/* format the host key into a nice userfriendly string. */
|
|
str = ssh_get_hexa(keyhash, keyhashlen);
|
|
ssh_clean_pubkey_hash(&keyhash);
|
|
|
|
return str;
|
|
}
|
|
|
|
static int
|
|
virCredTypeForPrompt(virConnectAuthPtr cred, char echo)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < cred->ncredtype; ++i) {
|
|
int type = cred->credtype[i];
|
|
if (echo) {
|
|
if (type == VIR_CRED_ECHOPROMPT)
|
|
return type;
|
|
} else {
|
|
if (type == VIR_CRED_PASSPHRASE ||
|
|
type == VIR_CRED_NOECHOPROMPT) {
|
|
return type;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
virLengthForPromptString(const char *str)
|
|
{
|
|
int len = strlen(str);
|
|
|
|
while (len > 0 && (str[len-1] == ' ' || str[len-1] == ':'))
|
|
--len;
|
|
|
|
return len;
|
|
}
|
|
|
|
/* check session host keys
|
|
*
|
|
* this function checks the known host database and verifies the key
|
|
* errors are raised in this func
|
|
*
|
|
* return value: 0 on success, -1 on error
|
|
*/
|
|
static int
|
|
virNetLibsshCheckHostKey(virNetLibsshSessionPtr sess)
|
|
{
|
|
int state;
|
|
char *keyhashstr;
|
|
const char *errmsg;
|
|
|
|
if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE)
|
|
return 0;
|
|
|
|
state = ssh_is_server_known(sess->session);
|
|
|
|
switch (state) {
|
|
case SSH_SERVER_KNOWN_OK:
|
|
/* host key matches */
|
|
return 0;
|
|
|
|
case SSH_SERVER_FOUND_OTHER:
|
|
case SSH_SERVER_KNOWN_CHANGED:
|
|
keyhashstr = virLibsshServerKeyAsString(sess);
|
|
if (!keyhashstr)
|
|
return -1;
|
|
|
|
/* host key verification failed */
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("!!! SSH HOST KEY VERIFICATION FAILED !!!: "
|
|
"Identity of host '%s:%d' differs from stored identity. "
|
|
"Please verify the new host key '%s' to avoid possible "
|
|
"man in the middle attack. The key is stored in '%s'."),
|
|
sess->hostname, sess->port,
|
|
keyhashstr, sess->knownHostsFile);
|
|
|
|
ssh_string_free_char(keyhashstr);
|
|
return -1;
|
|
|
|
case SSH_SERVER_FILE_NOT_FOUND:
|
|
case SSH_SERVER_NOT_KNOWN:
|
|
/* key was not found, query to add it to database */
|
|
if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL) {
|
|
virConnectCredential askKey;
|
|
int cred_type;
|
|
char *tmp;
|
|
|
|
/* ask to add the key */
|
|
if (!sess->cred || !sess->cred->cb) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("No user interaction callback provided: "
|
|
"Can't verify the session host key"));
|
|
return -1;
|
|
}
|
|
|
|
cred_type = virCredTypeForPrompt(sess->cred, 1 /* echo */);
|
|
if (cred_type == -1) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("no suitable callback for host key "
|
|
"verification"));
|
|
return -1;
|
|
}
|
|
|
|
/* prepare data for the callback */
|
|
memset(&askKey, 0, sizeof(virConnectCredential));
|
|
askKey.type = cred_type;
|
|
|
|
keyhashstr = virLibsshServerKeyAsString(sess);
|
|
if (!keyhashstr)
|
|
return -1;
|
|
|
|
if (virAsprintf(&tmp,
|
|
_("Accept SSH host key with hash '%s' for "
|
|
"host '%s:%d' (%s/%s)?"),
|
|
keyhashstr,
|
|
sess->hostname, sess->port,
|
|
"y", "n") < 0) {
|
|
ssh_string_free_char(keyhashstr);
|
|
return -1;
|
|
}
|
|
askKey.prompt = tmp;
|
|
|
|
if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to retrieve decision to accept "
|
|
"host key"));
|
|
VIR_FREE(tmp);
|
|
ssh_string_free_char(keyhashstr);
|
|
return -1;
|
|
}
|
|
|
|
VIR_FREE(tmp);
|
|
|
|
if (!askKey.result ||
|
|
STRCASENEQ(askKey.result, "y")) {
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("SSH host key for '%s' (%s) was not accepted"),
|
|
sess->hostname, keyhashstr);
|
|
ssh_string_free_char(keyhashstr);
|
|
VIR_FREE(askKey.result);
|
|
return -1;
|
|
}
|
|
ssh_string_free_char(keyhashstr);
|
|
VIR_FREE(askKey.result);
|
|
}
|
|
|
|
/* write the host key file, if specified */
|
|
if (sess->knownHostsFile) {
|
|
if (ssh_write_knownhost(sess->session) < 0) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to write known_host file '%s': %s"),
|
|
sess->knownHostsFile,
|
|
errmsg);
|
|
return -1;
|
|
}
|
|
}
|
|
/* key was accepted and added */
|
|
return 0;
|
|
|
|
case SSH_SERVER_ERROR:
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to validate SSH host key: %s"),
|
|
errmsg);
|
|
return -1;
|
|
|
|
default: /* should never happen (tm) */
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Unknown state of the remote server SSH key"));
|
|
return -1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* callback for ssh_pki_import_privkey_file, used to get the passphrase
|
|
* of a private key
|
|
*/
|
|
static int
|
|
virNetLibsshAuthenticatePrivkeyCb(const char *prompt,
|
|
char *buf,
|
|
size_t len,
|
|
int echo,
|
|
int verify ATTRIBUTE_UNUSED,
|
|
void *userdata)
|
|
{
|
|
virNetLibsshSessionPtr sess = userdata;
|
|
virConnectCredential retr_passphrase;
|
|
int cred_type;
|
|
char *actual_prompt = NULL;
|
|
int p;
|
|
|
|
/* request user's key password */
|
|
if (!sess->cred || !sess->cred->cb) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("No user interaction callback provided: "
|
|
"Can't retrieve private key passphrase"));
|
|
return -1;
|
|
}
|
|
|
|
cred_type = virCredTypeForPrompt(sess->cred, echo);
|
|
if (cred_type == -1) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("no suitable callback for input of key passphrase"));
|
|
goto error;
|
|
}
|
|
|
|
if (VIR_STRNDUP(actual_prompt, prompt,
|
|
virLengthForPromptString(prompt)) < 0)
|
|
goto error;
|
|
|
|
memset(&retr_passphrase, 0, sizeof(virConnectCredential));
|
|
retr_passphrase.type = cred_type;
|
|
retr_passphrase.prompt = actual_prompt;
|
|
|
|
if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to retrieve private key passphrase: "
|
|
"callback has failed"));
|
|
goto error;
|
|
}
|
|
|
|
p = virStrncpy(buf, retr_passphrase.result,
|
|
retr_passphrase.resultlen, len);
|
|
VIR_DISPOSE_STRING(retr_passphrase.result);
|
|
if (p < 0) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("passphrase is too long for the buffer"));
|
|
goto error;
|
|
}
|
|
|
|
VIR_FREE(actual_prompt);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
VIR_FREE(actual_prompt);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
virNetLibsshImportPrivkey(virNetLibsshSessionPtr sess,
|
|
virNetLibsshAuthMethodPtr priv,
|
|
ssh_key *ret_key)
|
|
{
|
|
int err;
|
|
int ret;
|
|
ssh_key key;
|
|
|
|
/* try open the key with the password set, first; since it can
|
|
* fail with SSH_ERROR also without the callback being called,
|
|
* reset the error so it is possible to check whether the callback
|
|
* failed or libssh did.
|
|
*/
|
|
virResetLastError();
|
|
ret = ssh_pki_import_privkey_file(priv->filename, priv->password,
|
|
virNetLibsshAuthenticatePrivkeyCb,
|
|
sess, &key);
|
|
if (ret == SSH_EOF) {
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("error while reading private key '%s'"),
|
|
priv->filename);
|
|
err = SSH_AUTH_ERROR;
|
|
goto error;
|
|
} else if (ret == SSH_ERROR) {
|
|
if (virGetLastErrorCode() == VIR_ERR_OK) {
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("error while opening private key '%s', wrong "
|
|
"passphrase?"),
|
|
priv->filename);
|
|
}
|
|
err = SSH_AUTH_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
*ret_key = key;
|
|
return SSH_AUTH_SUCCESS;
|
|
|
|
error:
|
|
return err;
|
|
}
|
|
|
|
|
|
/* perform private key authentication
|
|
*
|
|
* returns SSH_AUTH_* values
|
|
*/
|
|
static int
|
|
virNetLibsshAuthenticatePrivkey(virNetLibsshSessionPtr sess,
|
|
virNetLibsshAuthMethodPtr priv)
|
|
{
|
|
int err;
|
|
int ret;
|
|
char *tmp = NULL;
|
|
ssh_key public_key = NULL;
|
|
ssh_key private_key = NULL;
|
|
|
|
VIR_DEBUG("sess=%p", sess);
|
|
|
|
if (virAsprintf(&tmp, "%s.pub", priv->filename) < 0) {
|
|
err = SSH_AUTH_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
/* try to open the public part of the private key */
|
|
ret = ssh_pki_import_pubkey_file(tmp, &public_key);
|
|
if (ret == SSH_ERROR) {
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("error while reading public key '%s'"),
|
|
tmp);
|
|
err = SSH_AUTH_ERROR;
|
|
goto error;
|
|
} else if (ret == SSH_EOF) {
|
|
/* load the private key */
|
|
err = virNetLibsshImportPrivkey(sess, priv, &private_key);
|
|
if (err != SSH_AUTH_SUCCESS)
|
|
goto error;
|
|
|
|
/* create the public key from the private key */
|
|
ret = ssh_pki_export_privkey_to_pubkey(private_key, &public_key);
|
|
if (ret == SSH_ERROR) {
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("cannot export the public key from the "
|
|
"private key '%s'"),
|
|
priv->filename);
|
|
err = SSH_AUTH_ERROR;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
VIR_FREE(tmp);
|
|
|
|
ret = ssh_userauth_try_publickey(sess->session, NULL, public_key);
|
|
if (ret != SSH_AUTH_SUCCESS) {
|
|
err = SSH_AUTH_DENIED;
|
|
goto error;
|
|
}
|
|
|
|
/* load the private key, if it was not loaded yet */
|
|
if (private_key == NULL) {
|
|
err = virNetLibsshImportPrivkey(sess, priv, &private_key);
|
|
if (err != SSH_AUTH_SUCCESS)
|
|
goto error;
|
|
}
|
|
|
|
ret = ssh_userauth_publickey(sess->session, NULL, private_key);
|
|
if (ret != SSH_AUTH_SUCCESS) {
|
|
err = SSH_AUTH_DENIED;
|
|
goto error;
|
|
}
|
|
|
|
ssh_key_free(private_key);
|
|
ssh_key_free(public_key);
|
|
|
|
return SSH_AUTH_SUCCESS;
|
|
|
|
error:
|
|
if (private_key)
|
|
ssh_key_free(private_key);
|
|
if (public_key)
|
|
ssh_key_free(public_key);
|
|
VIR_FREE(tmp);
|
|
return err;
|
|
}
|
|
|
|
|
|
/* perform password authentication, either directly or request the password
|
|
*
|
|
* returns SSH_AUTH_* values
|
|
*/
|
|
static int
|
|
virNetLibsshAuthenticatePassword(virNetLibsshSessionPtr sess,
|
|
virNetLibsshAuthMethodPtr priv)
|
|
{
|
|
char *password = NULL;
|
|
const char *errmsg;
|
|
int ret = -1;
|
|
|
|
VIR_DEBUG("sess=%p", sess);
|
|
|
|
if (priv->password) {
|
|
/* tunelled password authentication */
|
|
if ((ret = ssh_userauth_password(sess->session, NULL,
|
|
priv->password)) == 0) {
|
|
ret = SSH_AUTH_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
/* password authentication with interactive password request */
|
|
if (!sess->cred || !sess->cred->cb) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Can't perform authentication: "
|
|
"Authentication callback not provided"));
|
|
ret = SSH_AUTH_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Try the authenticating the set amount of times. The server breaks the
|
|
* connection if maximum number of bad auth tries is exceeded */
|
|
while (true) {
|
|
if (!(password = virAuthGetPasswordPath(sess->authPath, sess->cred,
|
|
"ssh", sess->username,
|
|
sess->hostname))) {
|
|
ret = SSH_AUTH_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* tunelled password authentication */
|
|
if ((ret = ssh_userauth_password(sess->session, NULL,
|
|
password)) == 0) {
|
|
ret = SSH_AUTH_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
VIR_DISPOSE_STRING(password);
|
|
|
|
if (ret != SSH_AUTH_DENIED)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* error path */
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("authentication failed: %s"), errmsg);
|
|
|
|
return ret;
|
|
|
|
cleanup:
|
|
VIR_DISPOSE_STRING(password);
|
|
return ret;
|
|
}
|
|
|
|
/* perform keyboard interactive authentication
|
|
*
|
|
* returns SSH_AUTH_* values
|
|
*/
|
|
static int
|
|
virNetLibsshAuthenticateKeyboardInteractive(virNetLibsshSessionPtr sess,
|
|
virNetLibsshAuthMethodPtr priv)
|
|
{
|
|
int ret;
|
|
const char *errmsg;
|
|
int try = 0;
|
|
|
|
/* request user's key password */
|
|
if (!sess->cred || !sess->cred->cb) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("No user interaction callback provided: "
|
|
"Can't get input from keyboard interactive "
|
|
"authentication"));
|
|
return SSH_AUTH_ERROR;
|
|
}
|
|
|
|
again:
|
|
ret = ssh_userauth_kbdint(sess->session, NULL, NULL);
|
|
while (ret == SSH_AUTH_INFO) {
|
|
const char *name, *instruction;
|
|
int nprompts, iprompt;
|
|
virBuffer buff = VIR_BUFFER_INITIALIZER;
|
|
|
|
name = ssh_userauth_kbdint_getname(sess->session);
|
|
instruction = ssh_userauth_kbdint_getinstruction(sess->session);
|
|
nprompts = ssh_userauth_kbdint_getnprompts(sess->session);
|
|
|
|
/* compose the main buffer with name and instruction, if present */
|
|
if (name && name[0])
|
|
virBufferAddStr(&buff, name);
|
|
if (instruction && instruction[0]) {
|
|
if (virBufferUse(&buff) > 0)
|
|
virBufferAddChar(&buff, '\n');
|
|
virBufferAddStr(&buff, instruction);
|
|
}
|
|
if (virBufferUse(&buff) > 0)
|
|
virBufferAddChar(&buff, '\n');
|
|
|
|
if (virBufferCheckError(&buff) < 0)
|
|
return -1;
|
|
|
|
for (iprompt = 0; iprompt < nprompts; ++iprompt) {
|
|
virConnectCredential retr_passphrase;
|
|
const char *promptStr;
|
|
int promptStrLen;
|
|
char echo;
|
|
char *prompt = NULL;
|
|
int cred_type;
|
|
|
|
/* get the prompt */
|
|
promptStr = ssh_userauth_kbdint_getprompt(sess->session, iprompt,
|
|
&echo);
|
|
promptStrLen = virLengthForPromptString(promptStr);
|
|
|
|
cred_type = virCredTypeForPrompt(sess->cred, echo);
|
|
if (cred_type == -1) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("no suitable callback for input of keyboard "
|
|
"response"));
|
|
goto prompt_error;
|
|
}
|
|
|
|
/* create the prompt for the user, using the instruction
|
|
* buffer if specified
|
|
*/
|
|
if (virBufferUse(&buff) > 0) {
|
|
virBuffer prompt_buff = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAddBuffer(&prompt_buff, &buff);
|
|
virBufferAdd(&prompt_buff, promptStr, promptStrLen);
|
|
|
|
if (virBufferCheckError(&prompt_buff) < 0)
|
|
goto prompt_error;
|
|
|
|
prompt = virBufferContentAndReset(&prompt_buff);
|
|
} else {
|
|
if (VIR_STRNDUP(prompt, promptStr, promptStrLen) < 0)
|
|
goto prompt_error;
|
|
}
|
|
|
|
memset(&retr_passphrase, 0, sizeof(virConnectCredential));
|
|
retr_passphrase.type = cred_type;
|
|
retr_passphrase.prompt = prompt;
|
|
|
|
if (retr_passphrase.type == -1) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("no suitable callback for input of key "
|
|
"passphrase"));
|
|
goto prompt_error;
|
|
}
|
|
|
|
if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("failed to retrieve keyboard interactive "
|
|
"result: callback has failed"));
|
|
goto prompt_error;
|
|
}
|
|
|
|
VIR_FREE(prompt);
|
|
|
|
ret = ssh_userauth_kbdint_setanswer(sess->session, iprompt,
|
|
retr_passphrase.result);
|
|
VIR_DISPOSE_STRING(retr_passphrase.result);
|
|
if (ret < 0) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("authentication failed: %s"), errmsg);
|
|
goto prompt_error;
|
|
}
|
|
|
|
continue;
|
|
|
|
prompt_error:
|
|
VIR_FREE(prompt);
|
|
virBufferFreeAndReset(&buff);
|
|
return SSH_AUTH_ERROR;
|
|
}
|
|
|
|
virBufferFreeAndReset(&buff);
|
|
|
|
ret = ssh_userauth_kbdint(sess->session, NULL, NULL);
|
|
++try;
|
|
if (ret == SSH_AUTH_DENIED && (priv->tries < 0 || try < priv->tries))
|
|
goto again;
|
|
}
|
|
|
|
if (ret == SSH_AUTH_ERROR) {
|
|
/* error path */
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_AUTH_FAILED,
|
|
_("authentication failed: %s"), errmsg);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* select auth method and authenticate */
|
|
static int
|
|
virNetLibsshAuthenticate(virNetLibsshSessionPtr sess)
|
|
{
|
|
virNetLibsshAuthMethodPtr auth;
|
|
bool no_method = false;
|
|
bool auth_failed = false;
|
|
const char *errmsg;
|
|
int ret;
|
|
int methods;
|
|
size_t i;
|
|
|
|
VIR_DEBUG("sess=%p", sess);
|
|
|
|
/* At this point, we can assume there is at least one
|
|
* authentication method set -- virNetLibsshValidateConfig
|
|
* already checked that.
|
|
*/
|
|
|
|
/* try to authenticate */
|
|
ret = ssh_userauth_none(sess->session, NULL);
|
|
if (ret == SSH_AUTH_ERROR) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("Failed to authenticate as 'none': %s"),
|
|
errmsg);
|
|
return -1;
|
|
}
|
|
|
|
/* obtain list of supported auth methods */
|
|
methods = ssh_userauth_list(sess->session, NULL);
|
|
|
|
for (i = 0; i < sess->nauths; i++) {
|
|
auth = sess->auths[i];
|
|
|
|
if ((methods & auth->ssh_flags) == 0) {
|
|
no_method = true;
|
|
continue;
|
|
}
|
|
|
|
ret = SSH_AUTH_DENIED;
|
|
|
|
switch (auth->method) {
|
|
case VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE:
|
|
/* try to authenticate using the keyboard interactive way */
|
|
ret = virNetLibsshAuthenticateKeyboardInteractive(sess, auth);
|
|
break;
|
|
case VIR_NET_LIBSSH_AUTH_AGENT:
|
|
/* try to authenticate using ssh-agent */
|
|
ret = ssh_userauth_agent(sess->session, NULL);
|
|
if (ret == SSH_AUTH_ERROR) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to authenticate using agent: %s"),
|
|
errmsg);
|
|
}
|
|
break;
|
|
case VIR_NET_LIBSSH_AUTH_PRIVKEY:
|
|
/* try to authenticate using the provided ssh key */
|
|
ret = virNetLibsshAuthenticatePrivkey(sess, auth);
|
|
break;
|
|
case VIR_NET_LIBSSH_AUTH_PASSWORD:
|
|
/* try to authenticate with password */
|
|
ret = virNetLibsshAuthenticatePassword(sess, auth);
|
|
break;
|
|
}
|
|
|
|
if (ret == SSH_AUTH_ERROR) {
|
|
/* virReportError is called already */
|
|
return -1;
|
|
} else if (ret == SSH_AUTH_SUCCESS) {
|
|
/* authenticated */
|
|
return 0;
|
|
}
|
|
|
|
auth_failed = true;
|
|
}
|
|
|
|
if (sess->nauths == 1) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to authenticate: %s"),
|
|
errmsg);
|
|
} else if (no_method && !auth_failed) {
|
|
virReportError(VIR_ERR_AUTH_FAILED, "%s",
|
|
_("None of the requested authentication methods "
|
|
"are supported by the server"));
|
|
} else {
|
|
virReportError(VIR_ERR_AUTH_FAILED, "%s",
|
|
_("All provided authentication methods with credentials "
|
|
"were rejected by the server"));
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* open channel */
|
|
static int
|
|
virNetLibsshOpenChannel(virNetLibsshSessionPtr sess)
|
|
{
|
|
const char *errmsg;
|
|
|
|
sess->channel = ssh_channel_new(sess->session);
|
|
if (!sess->channel) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to create libssh channel: %s"),
|
|
errmsg);
|
|
return -1;
|
|
}
|
|
|
|
if (ssh_channel_open_session(sess->channel) != SSH_OK) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to open ssh channel: %s"),
|
|
errmsg);
|
|
return -1;
|
|
}
|
|
|
|
if (ssh_channel_request_exec(sess->channel, sess->channelCommand) != SSH_OK) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("failed to execute command '%s': %s"),
|
|
sess->channelCommand,
|
|
errmsg);
|
|
return -1;
|
|
}
|
|
|
|
/* nonblocking mode */
|
|
ssh_channel_set_blocking(sess->channel, 0);
|
|
|
|
/* channel open */
|
|
return 0;
|
|
}
|
|
|
|
/* validate if all required parameters are configured */
|
|
static int
|
|
virNetLibsshValidateConfig(virNetLibsshSessionPtr sess)
|
|
{
|
|
size_t i;
|
|
bool has_auths = false;
|
|
|
|
for (i = 0; i < sess->nauths; ++i) {
|
|
if (sess->auths[i]) {
|
|
has_auths = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_auths) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("No authentication methods and credentials "
|
|
"provided"));
|
|
return -1;
|
|
}
|
|
|
|
if (!sess->channelCommand) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("No channel command provided"));
|
|
return -1;
|
|
}
|
|
|
|
if (sess->hostKeyVerify != VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) {
|
|
if (!sess->hostname) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Hostname is needed for host key verification"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* everything ok */
|
|
return 0;
|
|
}
|
|
|
|
/* ### PUBLIC API ### */
|
|
int
|
|
virNetLibsshSessionAuthSetCallback(virNetLibsshSessionPtr sess,
|
|
virConnectAuthPtr auth)
|
|
{
|
|
virObjectLock(sess);
|
|
sess->cred = auth;
|
|
virObjectUnlock(sess);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionAuthAddPasswordAuth(virNetLibsshSessionPtr sess,
|
|
virURIPtr uri)
|
|
{
|
|
int ret;
|
|
virNetLibsshAuthMethodPtr auth;
|
|
|
|
if (uri) {
|
|
VIR_FREE(sess->authPath);
|
|
|
|
if (virAuthGetConfigFilePathURI(uri, &sess->authPath) < 0) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
auth->method = VIR_NET_LIBSSH_AUTH_PASSWORD;
|
|
auth->ssh_flags = SSH_AUTH_METHOD_PASSWORD;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionAuthAddAgentAuth(virNetLibsshSessionPtr sess)
|
|
{
|
|
int ret;
|
|
virNetLibsshAuthMethodPtr auth;
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
auth->method = VIR_NET_LIBSSH_AUTH_AGENT;
|
|
auth->ssh_flags = SSH_AUTH_METHOD_PUBLICKEY;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionAuthAddPrivKeyAuth(virNetLibsshSessionPtr sess,
|
|
const char *keyfile,
|
|
const char *password)
|
|
{
|
|
int ret;
|
|
virNetLibsshAuthMethodPtr auth;
|
|
char *pass = NULL;
|
|
char *file = NULL;
|
|
|
|
if (!keyfile) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Key file path must be provided "
|
|
"for private key authentication"));
|
|
ret = -1;
|
|
goto error;
|
|
}
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (VIR_STRDUP(file, keyfile) < 0 ||
|
|
VIR_STRDUP(pass, password) < 0) {
|
|
ret = -1;
|
|
goto error;
|
|
}
|
|
|
|
if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) {
|
|
ret = -1;
|
|
goto error;
|
|
}
|
|
|
|
auth->password = pass;
|
|
auth->filename = file;
|
|
auth->method = VIR_NET_LIBSSH_AUTH_PRIVKEY;
|
|
auth->ssh_flags = SSH_AUTH_METHOD_PUBLICKEY;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
|
|
error:
|
|
VIR_DISPOSE_STRING(pass);
|
|
VIR_FREE(file);
|
|
goto cleanup;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionAuthAddKeyboardAuth(virNetLibsshSessionPtr sess,
|
|
int tries)
|
|
{
|
|
int ret;
|
|
virNetLibsshAuthMethodPtr auth;
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
auth->tries = tries;
|
|
auth->method = VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE;
|
|
auth->ssh_flags = SSH_AUTH_METHOD_INTERACTIVE;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionSetChannelCommand(virNetLibsshSessionPtr sess,
|
|
const char *command)
|
|
{
|
|
int ret = 0;
|
|
virObjectLock(sess);
|
|
|
|
VIR_FREE(sess->channelCommand);
|
|
|
|
if (VIR_STRDUP(sess->channelCommand, command) < 0)
|
|
ret = -1;
|
|
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionSetHostKeyVerification(virNetLibsshSessionPtr sess,
|
|
const char *hostname,
|
|
int port,
|
|
const char *hostsfile,
|
|
virNetLibsshHostkeyVerify opt)
|
|
{
|
|
virObjectLock(sess);
|
|
|
|
sess->port = port;
|
|
sess->hostKeyVerify = opt;
|
|
|
|
VIR_FREE(sess->hostname);
|
|
|
|
if (VIR_STRDUP(sess->hostname, hostname) < 0)
|
|
goto error;
|
|
|
|
/* set the hostname */
|
|
if (ssh_options_set(sess->session, SSH_OPTIONS_HOST, sess->hostname) < 0)
|
|
goto error;
|
|
|
|
/* set the port */
|
|
if (port > 0) {
|
|
unsigned int portU = port;
|
|
|
|
if (ssh_options_set(sess->session, SSH_OPTIONS_PORT, &portU) < 0)
|
|
goto error;
|
|
}
|
|
|
|
/* set the known hosts file, if specified */
|
|
if (hostsfile) {
|
|
if (ssh_options_set(sess->session, SSH_OPTIONS_KNOWNHOSTS, hostsfile) < 0)
|
|
goto error;
|
|
|
|
VIR_FREE(sess->knownHostsFile);
|
|
if (VIR_STRDUP(sess->knownHostsFile, hostsfile) < 0)
|
|
goto error;
|
|
} else {
|
|
/* libssh does not support trying no known_host file at all:
|
|
* hence use /dev/null here, without storing it as file */
|
|
if (ssh_options_set(sess->session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null") < 0)
|
|
goto error;
|
|
}
|
|
|
|
virObjectUnlock(sess);
|
|
return 0;
|
|
|
|
error:
|
|
virObjectUnlock(sess);
|
|
return -1;
|
|
}
|
|
|
|
/* allocate and initialize a libssh session object */
|
|
virNetLibsshSessionPtr virNetLibsshSessionNew(const char *username)
|
|
{
|
|
virNetLibsshSessionPtr sess = NULL;
|
|
|
|
if (virNetLibsshSessionInitialize() < 0)
|
|
goto error;
|
|
|
|
if (!(sess = virObjectLockableNew(virNetLibsshSessionClass)))
|
|
goto error;
|
|
|
|
/* initialize session data */
|
|
if (!(sess->session = ssh_new())) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Failed to initialize libssh session"));
|
|
goto error;
|
|
}
|
|
|
|
if (VIR_STRDUP(sess->username, username) < 0)
|
|
goto error;
|
|
|
|
VIR_DEBUG("virNetLibsshSessionPtr: %p, ssh_session: %p",
|
|
sess, sess->session);
|
|
|
|
/* set blocking mode for libssh until handshake is complete */
|
|
ssh_set_blocking(sess->session, 1);
|
|
|
|
if (ssh_options_set(sess->session, SSH_OPTIONS_USER, sess->username) < 0)
|
|
goto error;
|
|
|
|
/* default states for config variables */
|
|
sess->state = VIR_NET_LIBSSH_STATE_NEW;
|
|
sess->hostKeyVerify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE;
|
|
|
|
return sess;
|
|
|
|
error:
|
|
virObjectUnref(sess);
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
virNetLibsshSessionConnect(virNetLibsshSessionPtr sess,
|
|
int sock)
|
|
{
|
|
int ret;
|
|
const char *errmsg;
|
|
|
|
VIR_DEBUG("sess=%p, sock=%d", sess, sock);
|
|
|
|
if (!sess || sess->state != VIR_NET_LIBSSH_STATE_NEW) {
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Invalid virNetLibsshSessionPtr"));
|
|
return -1;
|
|
}
|
|
|
|
virObjectLock(sess);
|
|
|
|
/* check if configuration is valid */
|
|
if ((ret = virNetLibsshValidateConfig(sess)) < 0)
|
|
goto error;
|
|
|
|
/* read ~/.ssh/config */
|
|
if ((ret = ssh_options_parse_config(sess->session, NULL)) < 0)
|
|
goto error;
|
|
|
|
/* set the socket FD for the libssh session */
|
|
if ((ret = ssh_options_set(sess->session, SSH_OPTIONS_FD, &sock)) < 0)
|
|
goto error;
|
|
|
|
/* open session */
|
|
ret = ssh_connect(sess->session);
|
|
/* libssh is in blocking mode, so EAGAIN will never happen */
|
|
if (ret < 0) {
|
|
errmsg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_NO_CONNECT,
|
|
_("SSH session handshake failed: %s"),
|
|
errmsg);
|
|
goto error;
|
|
}
|
|
|
|
/* verify the SSH host key */
|
|
if ((ret = virNetLibsshCheckHostKey(sess)) != 0)
|
|
goto error;
|
|
|
|
/* authenticate */
|
|
if ((ret = virNetLibsshAuthenticate(sess)) != 0)
|
|
goto error;
|
|
|
|
/* open channel */
|
|
if ((ret = virNetLibsshOpenChannel(sess)) != 0)
|
|
goto error;
|
|
|
|
/* all set */
|
|
/* switch to nonblocking mode and return */
|
|
ssh_set_blocking(sess->session, 0);
|
|
sess->state = VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE;
|
|
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
|
|
error:
|
|
sess->state = VIR_NET_LIBSSH_STATE_ERROR;
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
/* do a read from a ssh channel, used instead of normal read on socket */
|
|
ssize_t
|
|
virNetLibsshChannelRead(virNetLibsshSessionPtr sess,
|
|
char *buf,
|
|
size_t len)
|
|
{
|
|
int ret = -1;
|
|
ssize_t read_n = 0;
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) {
|
|
if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE)
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("Remote program terminated "
|
|
"with non-zero code: %d"),
|
|
sess->channelCommandReturnValue);
|
|
else
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Tried to write socket in error state"));
|
|
|
|
virObjectUnlock(sess);
|
|
return -1;
|
|
}
|
|
|
|
if (sess->bufUsed > 0) {
|
|
/* copy the rest (or complete) internal buffer to the output buffer */
|
|
memcpy(buf,
|
|
sess->rbuf + sess->bufStart,
|
|
len > sess->bufUsed ? sess->bufUsed : len);
|
|
|
|
if (len >= sess->bufUsed) {
|
|
read_n = sess->bufUsed;
|
|
|
|
sess->bufStart = 0;
|
|
sess->bufUsed = 0;
|
|
} else {
|
|
read_n = len;
|
|
sess->bufUsed -= len;
|
|
sess->bufStart += len;
|
|
|
|
goto success;
|
|
}
|
|
}
|
|
|
|
/* continue reading into the buffer supplied */
|
|
if (read_n < len) {
|
|
ret = ssh_channel_read_nonblocking(sess->channel,
|
|
buf + read_n,
|
|
len - read_n,
|
|
0);
|
|
|
|
if (ret == SSH_EOF || (ret == 0 && ssh_channel_is_eof(sess->channel)))
|
|
goto eof;
|
|
|
|
if (ret == SSH_AGAIN)
|
|
goto success;
|
|
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
read_n += ret;
|
|
}
|
|
|
|
/* try to read something into the internal buffer */
|
|
if (sess->bufUsed == 0) {
|
|
ret = ssh_channel_read_nonblocking(sess->channel,
|
|
sess->rbuf,
|
|
VIR_NET_LIBSSH_BUFFER_SIZE,
|
|
0);
|
|
|
|
if (ret == SSH_EOF || (ret == 0 && ssh_channel_is_eof(sess->channel)))
|
|
goto eof;
|
|
|
|
if (ret == SSH_AGAIN)
|
|
goto success;
|
|
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
sess->bufUsed = ret;
|
|
sess->bufStart = 0;
|
|
}
|
|
|
|
if (read_n == 0) {
|
|
/* get rid of data in stderr stream */
|
|
ret = ssh_channel_read_nonblocking(sess->channel,
|
|
sess->rbuf,
|
|
VIR_NET_LIBSSH_BUFFER_SIZE - 1,
|
|
1);
|
|
if (ret > 0) {
|
|
sess->rbuf[ret] = '\0';
|
|
VIR_DEBUG("flushing stderr, data='%s'", sess->rbuf);
|
|
}
|
|
}
|
|
|
|
if (ssh_channel_is_eof(sess->channel)) {
|
|
eof:
|
|
if (ssh_channel_get_exit_status(sess->channel)) {
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("Remote command terminated with non-zero code: %d"),
|
|
ssh_channel_get_exit_status(sess->channel));
|
|
sess->channelCommandReturnValue = ssh_channel_get_exit_status(sess->channel);
|
|
sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE;
|
|
virObjectUnlock(sess);
|
|
return -1;
|
|
}
|
|
|
|
sess->state = VIR_NET_LIBSSH_STATE_CLOSED;
|
|
virObjectUnlock(sess);
|
|
return -1;
|
|
}
|
|
|
|
success:
|
|
virObjectUnlock(sess);
|
|
return read_n;
|
|
|
|
error:
|
|
sess->state = VIR_NET_LIBSSH_STATE_ERROR;
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
ssize_t
|
|
virNetLibsshChannelWrite(virNetLibsshSessionPtr sess,
|
|
const char *buf,
|
|
size_t len)
|
|
{
|
|
ssize_t ret;
|
|
|
|
virObjectLock(sess);
|
|
|
|
if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) {
|
|
if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE)
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("Remote program terminated with non-zero code: %d"),
|
|
sess->channelCommandReturnValue);
|
|
else
|
|
virReportError(VIR_ERR_LIBSSH, "%s",
|
|
_("Tried to write socket in error state"));
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ssh_channel_is_eof(sess->channel)) {
|
|
if (ssh_channel_get_exit_status(sess->channel)) {
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("Remote program terminated with non-zero code: %d"),
|
|
ssh_channel_get_exit_status(sess->channel));
|
|
sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE;
|
|
sess->channelCommandReturnValue = ssh_channel_get_exit_status(sess->channel);
|
|
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
sess->state = VIR_NET_LIBSSH_STATE_CLOSED;
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = ssh_channel_write(sess->channel, buf, len);
|
|
if (ret == SSH_AGAIN) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
const char *msg;
|
|
sess->state = VIR_NET_LIBSSH_STATE_ERROR;
|
|
msg = ssh_get_error(sess->session);
|
|
virReportError(VIR_ERR_LIBSSH,
|
|
_("write failed: %s"), msg);
|
|
}
|
|
|
|
cleanup:
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
virNetLibsshSessionHasCachedData(virNetLibsshSessionPtr sess)
|
|
{
|
|
bool ret;
|
|
|
|
if (!sess)
|
|
return false;
|
|
|
|
virObjectLock(sess);
|
|
|
|
ret = sess->bufUsed > 0;
|
|
|
|
virObjectUnlock(sess);
|
|
return ret;
|
|
}
|