diff --git a/configure.ac b/configure.ac index d65062642e..e7d36bc6b0 100644 --- a/configure.ac +++ b/configure.ac @@ -112,6 +112,7 @@ OPENWSMAN_REQUIRED="2.2.3" LIBPCAP_REQUIRED="1.0.0" LIBNL_REQUIRED="1.1" LIBSSH2_REQUIRED="1.0" +LIBSSH2_TRANSPORT_REQUIRED="1.3" LIBBLKID_REQUIRED="2.17" DBUS_REQUIRED="1.0.0" @@ -440,6 +441,8 @@ AC_ARG_WITH([console-lock-files], (use auto for default paths on some platforms) @<:@default=auto@:>@]), [],[with_console_lock_files=auto]) +AC_ARG_WITH([libssh2_transport], + AC_HELP_STRING([--with-libssh2_transport], [libssh2 location @<:@default=check@:>@]),[],[with_libssh2_transport=check]) dnl dnl in case someone want to build static binaries @@ -1748,29 +1751,58 @@ AM_CONDITIONAL([WITH_UML], [test "$with_uml" = "yes"]) dnl -dnl check for libssh2 (PHYP) +dnl check for libssh2 (PHYP and libssh2 transport) dnl LIBSSH2_CFLAGS="" LIBSSH2_LIBS="" -if test "$with_phyp" = "yes" || test "$with_phyp" = "check"; then +if test "$with_phyp" = "yes" || test "$with_phyp" = "check" || + test "$with_libssh2_transport" = "yes" || test "$with_libssh2_transport" = "check"; then PKG_CHECK_MODULES([LIBSSH2], [libssh2 >= $LIBSSH2_REQUIRED], [ - with_phyp=yes + if test "$with_phyp" = "check"; then + with_phyp=yes + fi + if $PKG_CONFIG "libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED"; then + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=yes + fi + else + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=no + AC_MSG_NOTICE([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + if test "$with_libssh2_transport" = "yes"; then + AC_MSG_ERROR([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + fi ], [ if test "$with_phyp" = "check"; then with_phyp=no AC_MSG_NOTICE([libssh2 is required for Phyp driver, disabling it]) - else + fi + if test "$with_phyp" = "yes"; then AC_MSG_ERROR([libssh2 >= $LIBSSH2_REQUIRED is required for Phyp driver]) fi + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=no + AC_MSG_NOTICE([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + if test "$with_libssh2_transport" = "yes"; then + AC_MSG_ERROR([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi ]) fi if test "$with_phyp" = "yes"; then AC_DEFINE_UNQUOTED([WITH_PHYP], 1, [whether IBM HMC / IVM driver is enabled]) fi +if test "$with_libssh2_transport" = "yes"; then + AC_DEFINE_UNQUOTED([HAVE_LIBSSH2], 1, [whether libssh2 transport is enabled]) +fi + AM_CONDITIONAL([WITH_PHYP],[test "$with_phyp" = "yes"]) +AM_CONDITIONAL([HAVE_LIBSSH2], [test "$with_libssh2_transport" = "yes"]) AC_SUBST([LIBSSH2_CFLAGS]) AC_SUBST([LIBSSH2_LIBS]) diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index d0af43d775..69c64aa363 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -112,6 +112,8 @@ typedef enum { VIR_FROM_PARALLELS = 48, /* Error from Parallels */ VIR_FROM_DEVICE = 49, /* Error from Device */ + VIR_FROM_SSH = 50, /* Error from libssh2 connection transport */ + # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST # endif @@ -280,6 +282,7 @@ typedef enum { VIR_ERR_BLOCK_COPY_ACTIVE = 83, /* action prevented by block copy job */ VIR_ERR_OPERATION_UNSUPPORTED = 84, /* The requested operation is not supported */ + VIR_ERR_SSH = 85, /* error in ssh transport driver */ } virErrorNumber; /** diff --git a/po/POTFILES.in b/po/POTFILES.in index 20d9d82b38..6c30116692 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -100,6 +100,7 @@ src/rpc/virnetserver.c src/rpc/virnetserverclient.c src/rpc/virnetservermdns.c src/rpc/virnetserverprogram.c +src/rpc/virnetsshsession.c src/rpc/virnettlscontext.c src/secret/secret_driver.c src/security/security_apparmor.c diff --git a/src/Makefile.am b/src/Makefile.am index d35edd639f..e90be304f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1323,6 +1323,10 @@ if HAVE_SASL USED_SYM_FILES += libvirt_sasl.syms endif +if HAVE_LIBSSH2 +USED_SYM_FILES += libvirt_libssh2.syms +endif + if WITH_ATOMIC_OPS_PTHREAD USED_SYM_FILES += libvirt_atomic.syms endif @@ -1339,7 +1343,8 @@ EXTRA_DIST += \ libvirt_qemu.syms \ libvirt_sasl.syms \ libvirt_vmx.syms \ - libvirt_xenxs.syms + libvirt_xenxs.syms \ + libvirt_libssh2.syms GENERATED_SYM_FILES = libvirt.syms libvirt.def libvirt_qemu.def @@ -1518,6 +1523,13 @@ libvirt_net_rpc_la_SOURCES = \ rpc/virnettlscontext.h rpc/virnettlscontext.c \ rpc/virkeepaliveprotocol.h rpc/virkeepaliveprotocol.c \ rpc/virkeepalive.h rpc/virkeepalive.c +if HAVE_LIBSSH2 +libvirt_net_rpc_la_SOURCES += \ + rpc/virnetsshsession.h rpc/virnetsshsession.c +else +EXTRA_DIST += \ + rpc/virnetsshsession.h rpc/virnetsshsession.c +endif if HAVE_SASL libvirt_net_rpc_la_SOURCES += \ rpc/virnetsaslcontext.h rpc/virnetsaslcontext.c @@ -1528,11 +1540,13 @@ endif libvirt_net_rpc_la_CFLAGS = \ $(GNUTLS_CFLAGS) \ $(SASL_CFLAGS) \ + $(LIBSSH2_CFLAGS) \ $(XDR_CFLAGS) \ $(AM_CFLAGS) libvirt_net_rpc_la_LDFLAGS = \ $(GNUTLS_LIBS) \ $(SASL_LIBS) \ + $(LIBSSH2_LIBS)\ $(AM_LDFLAGS) \ $(CYGWIN_EXTRA_LDFLAGS) \ $(MINGW_EXTRA_LDFLAGS) diff --git a/src/libvirt_libssh2.syms b/src/libvirt_libssh2.syms new file mode 100644 index 0000000000..fae0cba33c --- /dev/null +++ b/src/libvirt_libssh2.syms @@ -0,0 +1,18 @@ +# +# ssh session - specific symbols +# + +# virnetsshsession.h +# +virNetSSHChannelRead; +virNetSSHChannelWrite; +virNetSSHSessionAuthAddAgentAuth; +virNetSSHSessionAuthAddKeyboardAuth; +virNetSSHSessionAuthAddPasswordAuth; +virNetSSHSessionAuthAddPrivKeyAuth; +virNetSSHSessionAuthReset; +virNetSSHSessionAuthSetCallback; +virNetSSHSessionConnect; +virNetSSHSessionHasCachedData; +virNetSSHSessionSetChannelCommand; +virNetSSHSessionSetHostKeyVerification; diff --git a/src/rpc/virnetsshsession.c b/src/rpc/virnetsshsession.c new file mode 100644 index 0000000000..fe0197e683 --- /dev/null +++ b/src/rpc/virnetsshsession.c @@ -0,0 +1,1472 @@ +/* + * virnetsshsession.c: ssh network transport provider based on libssh2 + * + * Copyright (C) 2012 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 + * . + * + * Author: Peter Krempa + */ +#include +#include +#include + +#include "virnetsshsession.h" + +#include "internal.h" +#include "buf.h" +#include "memory.h" +#include "logging.h" +#include "configmake.h" +#include "threads.h" +#include "util.h" +#include "virterror_internal.h" +#include "virobject.h" + +#define VIR_FROM_THIS VIR_FROM_SSH + +static const char +vir_libssh2_key_comment[] = "added by libvirt ssh transport"; +#define VIR_NET_SSH_BUFFER_SIZE 1024 + +typedef enum { + VIR_NET_SSH_STATE_NEW, + VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE, + VIR_NET_SSH_STATE_AUTH_CALLBACK_ERROR, + VIR_NET_SSH_STATE_CLOSED, + VIR_NET_SSH_STATE_ERROR, + VIR_NET_SSH_STATE_ERROR_REMOTE, +} virNetSSHSessionState; + +typedef enum { + VIR_NET_SSH_AUTHCB_OK, + VIR_NET_SSH_AUTHCB_NO_METHOD, + VIR_NET_SSH_AUTHCB_OOM, + VIR_NET_SSH_AUTHCB_RETR_ERR, +} virNetSSHAuthCallbackError; + +typedef enum { + VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE, + VIR_NET_SSH_AUTH_PASSWORD, + VIR_NET_SSH_AUTH_PRIVKEY, + VIR_NET_SSH_AUTH_AGENT +} virNetSSHAuthMethods; + + +typedef struct _virNetSSHAuthMethod virNetSSHAuthMethod; +typedef virNetSSHAuthMethod *virNetSSHAuthMethodPtr; + +struct _virNetSSHAuthMethod { + virNetSSHAuthMethods method; + char *username; + char *password; + char *filename; + + int tries; +}; + +struct _virNetSSHSession { + virObject object; + virNetSSHSessionState state; + virMutex lock; + + /* libssh2 internal stuff */ + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + LIBSSH2_KNOWNHOSTS *knownHosts; + LIBSSH2_AGENT *agent; + + /* for host key checking */ + virNetSSHHostkeyVerify hostKeyVerify; + char *knownHostsFile; + char *hostname; + int port; + + /* authentication stuff */ + virConnectAuthPtr cred; + virNetSSHAuthCallbackError authCbErr; + size_t nauths; + virNetSSHAuthMethodPtr *auths; + + /* channel stuff */ + char *channelCommand; + int channelCommandReturnValue; + + /* read cache */ + char rbuf[VIR_NET_SSH_BUFFER_SIZE]; + size_t bufUsed; + size_t bufStart; +}; + +static void +virNetSSHSessionAuthMethodsFree(virNetSSHSessionPtr sess) +{ + int i; + + for (i = 0; i < sess->nauths; i++) { + VIR_FREE(sess->auths[i]->username); + VIR_FREE(sess->auths[i]->password); + VIR_FREE(sess->auths[i]->filename); + VIR_FREE(sess->auths[i]); + } + + VIR_FREE(sess->auths); + sess->nauths = 0; +} + +static void +virNetSSHSessionDispose(void *obj) +{ + virNetSSHSessionPtr sess = obj; + VIR_DEBUG("sess=0x%p", sess); + + if (!sess) + return; + + if (sess->channel) { + libssh2_channel_send_eof(sess->channel); + libssh2_channel_close(sess->channel); + libssh2_channel_free(sess->channel); + } + + libssh2_knownhost_free(sess->knownHosts); + libssh2_agent_free(sess->agent); + + if (sess->session) { + libssh2_session_disconnect(sess->session, + "libvirt: virNetSSHSessionFree()"); + libssh2_session_free(sess->session); + } + + virNetSSHSessionAuthMethodsFree(sess); + + VIR_FREE(sess->channelCommand); + VIR_FREE(sess->hostname); + VIR_FREE(sess->knownHostsFile); +} + +static virClassPtr virNetSSHSessionClass; +static int +virNetSSHSessionOnceInit(void) +{ + if (!(virNetSSHSessionClass = virClassNew("virNetSSHSession", + sizeof(virNetSSHSession), + virNetSSHSessionDispose))) + return -1; + + return 0; +} +VIR_ONCE_GLOBAL_INIT(virNetSSHSession); + +static virNetSSHAuthMethodPtr +virNetSSHSessionAuthMethodNew(virNetSSHSessionPtr sess) +{ + virNetSSHAuthMethodPtr 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; +} + +/* keyboard interactive authentication callback */ +static void +virNetSSHKbIntCb(const char *name ATTRIBUTE_UNUSED, + int name_len ATTRIBUTE_UNUSED, + const char *instruction ATTRIBUTE_UNUSED, + int instruction_len ATTRIBUTE_UNUSED, + int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **opaque) +{ + virNetSSHSessionPtr priv = *opaque; + virConnectCredentialPtr askcred = NULL; + int i; + int credtype_echo = -1; + int credtype_noecho = -1; + char *tmp; + + priv->authCbErr = VIR_NET_SSH_AUTHCB_OK; + + /* find credential type for asking passwords */ + for (i = 0; i < priv->cred->ncredtype; i++) { + if (priv->cred->credtype[i] == VIR_CRED_PASSPHRASE || + priv->cred->credtype[i] == VIR_CRED_NOECHOPROMPT) + credtype_noecho = priv->cred->credtype[i]; + + if (priv->cred->credtype[i] == VIR_CRED_ECHOPROMPT) + credtype_echo = priv->cred->credtype[i]; + } + + if (credtype_echo < 0 || credtype_noecho < 0) { + priv->authCbErr = VIR_NET_SSH_AUTHCB_NO_METHOD; + return; + } + + if (VIR_ALLOC_N(askcred, num_prompts) < 0) { + priv->authCbErr = VIR_NET_SSH_AUTHCB_OOM; + return; + } + + /* fill data structures for auth callback */ + for (i = 0; i < num_prompts; i++) { + if (!(askcred[i].prompt = strdup(prompts[i].text))) { + priv->authCbErr = VIR_NET_SSH_AUTHCB_OOM; + goto cleanup; + } + + /* remove colon and trailing spaces from prompts, as default behavior + * of libvirt's auth callback is to add them */ + if ((tmp = strrchr(askcred[i].prompt, ':'))) + *tmp = '\0'; + + askcred[i].type = prompts[i].echo ? credtype_echo : credtype_noecho; + } + + /* retrieve responses using the auth callback */ + if (priv->cred->cb(askcred, num_prompts, priv->cred->cbdata)) { + priv->authCbErr = VIR_NET_SSH_AUTHCB_RETR_ERR; + goto cleanup; + } + + /* copy retrieved data back */ + for (i = 0; i < num_prompts; i++) { + responses[i].text = askcred[i].result; + askcred[i].result = NULL; /* steal the pointer */ + responses[i].length = askcred[i].resultlen; + } + +cleanup: + if (askcred) { + for (i = 0; i < num_prompts; i++) { + VIR_FREE(askcred[i].result); + VIR_FREE(askcred[i].prompt); + } + } + + VIR_FREE(askcred); + + return; +} + +/* 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 +virNetSSHCheckHostKey(virNetSSHSessionPtr sess) +{ + int ret; + const char *key; + const char *keyhash; + int keyType; + size_t keyLength; + char *errmsg; + virBuffer buff = VIR_BUFFER_INITIALIZER; + virConnectCredential askKey; + struct libssh2_knownhost *knownHostEntry = NULL; + int i; + char *hostnameStr = NULL; + + if (sess->hostKeyVerify == VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE) + return 0; + + /* get the key */ + key = libssh2_session_hostkey(sess->session, &keyLength, &keyType); + if (!key) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("Failed to retrieve ssh host key: %s"), + errmsg); + return -1; + } + + /* verify it */ + ret = libssh2_knownhost_checkp(sess->knownHosts, + sess->hostname, + sess->port, + key, + keyLength, + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW, + &knownHostEntry); + + switch (ret) { + case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: + /* key was not found, query to add it to database */ + if (sess->hostKeyVerify == VIR_NET_SSH_HOSTKEY_VERIFY_NORMAL) { + /* ask to add the key */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_SSH, "%s", + _("No user interaction callback provided: " + "Can't verify the session host key")); + return -1; + } + + /* prepare data for the callback */ + memset(&askKey, 0, sizeof(virConnectCredential)); + + for (i = 0; i < sess->cred->ncredtype; i++) { + if (sess->cred->credtype[i] == VIR_CRED_ECHOPROMPT) { + i = -1; + break; + } + } + + if (i > 0) { + virReportError(VIR_ERR_SSH, "%s", + _("no suitable method to retrieve " + "authentication cretentials")); + return -1; + } + + /* calculate remote key hash, using MD5 algorithm that is + * usual in OpenSSH. The returned value should *NOT* be freed*/ + if (!(keyhash = libssh2_hostkey_hash(sess->session, + LIBSSH2_HOSTKEY_HASH_MD5))) { + virReportError(VIR_ERR_SSH, "%s", + _("failed to calculate ssh host key hash")); + return -1; + } + /* format the host key into a nice userfriendly string. + * Sadly, there's no constant to describe the hash length, so + * we have to use a *MAGIC* constant. */ + for (i = 0; i < 16; i++) + virBufferAsprintf(&buff, "%02hhX:", keyhash[i]); + virBufferTrim(&buff, ":", 1); + + if (virBufferError(&buff) != 0) { + virReportOOMError(); + return -1; + } + + keyhash = virBufferContentAndReset(&buff); + + askKey.type = VIR_CRED_ECHOPROMPT; + if (virAsprintf((char **)&askKey.prompt, + _("Accept SSH host key with hash '%s' for " + "host '%s:%d' (%s/%s)?"), + keyhash, + sess->hostname, sess->port, + "y", "n") < 0) { + virReportOOMError(); + VIR_FREE(keyhash); + return -1; + } + + if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_SSH, "%s", + _("failed to retrieve decision to accept " + "host key")); + VIR_FREE(askKey.prompt); + VIR_FREE(keyhash); + return -1; + } + + VIR_FREE(askKey.prompt); + + if (!askKey.result || + STRCASENEQ(askKey.result, "y")) { + virReportError(VIR_ERR_SSH, + _("SSH host key for '%s' (%s) was not accepted"), + sess->hostname, keyhash); + VIR_FREE(keyhash); + VIR_FREE(askKey.result); + return -1; + } + VIR_FREE(keyhash); + VIR_FREE(askKey.result); + } + + /* VIR_NET_SSH_HOSTKEY_VERIFY_AUTO_ADD */ + /* convert key type, as libssh is using different enums type for + * getting the key and different for adding ... */ + switch (keyType) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + keyType = LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + keyType = LIBSSH2_KNOWNHOST_KEY_SSHDSS; + + case LIBSSH2_HOSTKEY_TYPE_UNKNOWN: + default: + virReportError(VIR_ERR_SSH, "%s", + _("unsupported SSH key type")); + return -1; + } + + /* add the key to the DB and save it, if applicable */ + /* construct a "[hostname]:port" string to have the hostkey bound + * to port number */ + virBufferAsprintf(&buff, "[%s]:%d", sess->hostname, sess->port); + + if (virBufferError(&buff) != 0) { + virReportOOMError(); + return -1; + } + + hostnameStr = virBufferContentAndReset(&buff); + + if (libssh2_knownhost_addc(sess->knownHosts, + hostnameStr, + NULL, + key, + keyLength, + vir_libssh2_key_comment, + strlen(vir_libssh2_key_comment), + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW | + keyType, + NULL) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("unable to add SSH host key for host '%s': %s"), + hostnameStr, errmsg); + VIR_FREE(hostnameStr); + return -1; + } + + VIR_FREE(hostnameStr); + + /* write the host key file - if applicable */ + if (sess->knownHostsFile) { + if (libssh2_knownhost_writefile(sess->knownHosts, + sess->knownHostsFile, + LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("failed to write known_host file '%s': %s"), + sess->knownHostsFile, + errmsg); + return -1; + } + } + /* key was accepted and added */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MATCH: + /* host key matches */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: + /* 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, + knownHostEntry->key, sess->knownHostsFile); + return -1; + + case LIBSSH2_KNOWNHOST_CHECK_FAILURE: + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("failed to validate SSH host key: %s"), + errmsg); + return -1; + + default: /* should never happen (tm) */ + virReportError(VIR_ERR_SSH, "%s", _("Unknown error value")); + return -1; + } + + return -1; +} + +/* perform ssh agent authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetSSHAuthenticateAgent(virNetSSHSessionPtr sess, + virNetSSHAuthMethodPtr priv) +{ + struct libssh2_agent_publickey *agent_identity = NULL; + bool no_identity = true; + int ret; + char *errmsg; + + if (libssh2_agent_connect(sess->agent) < 0) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to connect to ssh agent")); + return 1; + } + + if (libssh2_agent_list_identities(sess->agent) < 0) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to list ssh agent identities")); + return 1; + } + + while (!(ret = libssh2_agent_get_identity(sess->agent, + &agent_identity, + agent_identity))) { + no_identity = false; + if (!(ret = libssh2_agent_userauth(sess->agent, + priv->username, + agent_identity))) + return 0; /* key accepted */ + + if (ret != LIBSSH2_ERROR_AUTHENTICATION_FAILED && + ret != LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED && + ret != LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("failed to authenticate using SSH agent: %s"), + errmsg); + return -1; + } + /* authentication has failed, try next key */ + } + + /* if there are no more keys in the agent, the identity retrieval + * function returns 1 */ + if (ret == 1) { + if (no_identity) { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("SSH Agent did not provide any " + "authentication identity")); + } else { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("All identities provided by the SSH Agent " + "were rejected")); + } + return 1; + } + + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("failed to authenticate using SSH agent: %s"), + errmsg); + return -1; +} + +/* perform private key authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetSSHAuthenticatePrivkey(virNetSSHSessionPtr sess, + virNetSSHAuthMethodPtr priv) +{ + virConnectCredential retr_passphrase; + int i; + char *errmsg; + int ret; + + /* try open the key with no password */ + if ((ret = libssh2_userauth_publickey_fromfile(sess->session, + priv->username, + NULL, + priv->filename, + priv->password)) == 0) + return 0; /* success */ + + if (priv->password || + ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED || + ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication with private key '%s' " + "has failed: %s"), + priv->filename, errmsg); + return 1; /* auth failed */ + } + + /* request user's key password */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_SSH, "%s", + _("No user interaction callback provided: " + "Can't retrieve private key passphrase")); + return -1; + } + + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); + retr_passphrase.type = -1; + + for (i = 0; i < sess->cred->ncredtype; i++) { + if (sess->cred->credtype[i] == VIR_CRED_PASSPHRASE || + sess->cred->credtype[i] == VIR_CRED_NOECHOPROMPT) { + retr_passphrase.type = sess->cred->credtype[i]; + break; + } + } + + if (retr_passphrase.type < 0) { + virReportError(VIR_ERR_SSH, "%s", + _("no suitable method to retrieve key passphrase")); + return -1; + } + + if (virAsprintf((char **)&retr_passphrase.prompt, + _("Passphrase for key '%s'"), + priv->filename) < 0) { + virReportOOMError(); + return -1; + } + + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_SSH, "%s", + _("failed to retrieve private key passphrase: " + "callback has failed")); + VIR_FREE(retr_passphrase.prompt); + return -1; + } + + VIR_FREE(retr_passphrase.prompt); + + ret = libssh2_userauth_publickey_fromfile(sess->session, + priv->username, + NULL, + priv->filename, + retr_passphrase.result); + + VIR_FREE(retr_passphrase.result); + + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication with private key '%s' " + "has failed: %s"), + priv->filename, errmsg); + + if (ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED || + ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + return 1; + else + return -1; + } + + return 0; +} + +/* perform tunelled password authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetSSHAuthenticatePassword(virNetSSHSessionPtr sess, + virNetSSHAuthMethodPtr priv) +{ + char *errmsg; + int ret; + + /* tunelled password authentication */ + if ((ret = libssh2_userauth_password(sess->session, + priv->username, + priv->password)) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("tunelled password authentication failed: %s"), + errmsg); + + if (ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + return 1; + else + return -1; + } + /* auth success */ + return 0; +} + +/* perform keyboard interactive authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetSSHAuthenticateKeyboardInteractive(virNetSSHSessionPtr sess, + virNetSSHAuthMethodPtr priv) +{ + char *errmsg; + int ret; + + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_SSH, "%s", + _("Can't perform keyboard-interactive authentication: " + "Authentication callback not provided ")); + return -1; + } + + /* Try the authenticating the set amount of times. The server breaks the + * connection if maximum number of bad auth tries is exceeded */ + while (priv->tries < 0 || priv->tries-- > 0) { + ret = libssh2_userauth_keyboard_interactive(sess->session, + priv->username, + virNetSSHKbIntCb); + + /* check for errors while calling the callback */ + switch (sess->authCbErr) { + case VIR_NET_SSH_AUTHCB_NO_METHOD: + virReportError(VIR_ERR_SSH, "%s", + _("no suitable method to retrieve " + "authentication cretentials")); + return -1; + case VIR_NET_SSH_AUTHCB_OOM: + virReportOOMError(); + return -1; + case VIR_NET_SSH_AUTHCB_RETR_ERR: + virReportError(VIR_ERR_SSH, "%s", + _("failed to retrieve credentials")); + return -1; + case VIR_NET_SSH_AUTHCB_OK: + /* everything went fine, let's continue */ + break; + } + + if (ret == 0) + /* authentication succeeded */ + return 0; + + if (ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + continue; /* try again */ + + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("keyboard interactive authentication failed: %s"), + errmsg); + return -1; + } + } + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("keyboard interactive authentication failed: %s"), + errmsg); + return 1; +} + +/* select auth method and authenticate */ +static int +virNetSSHAuthenticate(virNetSSHSessionPtr sess) +{ + virNetSSHAuthMethodPtr auth; + bool no_method = false; + bool auth_failed = false; + char *auth_list; + char *errmsg; + int i; + int ret; + + if (!sess->nauths) { + virReportError(VIR_ERR_SSH, "%s", + _("No authentication methods and credentials " + "provided")); + return -1; + } + + /* obtain list of supported auth methods */ + auth_list = libssh2_userauth_list(sess->session, + sess->auths[0]->username, + strlen(sess->auths[0]->username)); + if (!auth_list) { + /* unlikely event, authentication succeeded with NONE as method */ + if (libssh2_userauth_authenticated(sess->session) == 1) + return 0; + + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("couldn't retrieve authentication methods list: %s"), + errmsg); + return -1; + } + + for (i = 0; i < sess->nauths; i++) { + auth = sess->auths[i]; + + ret = 2; + virResetLastError(); + + switch (auth->method) { + case VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE: + if (strstr(auth_list, "keyboard-interactive")) + ret = virNetSSHAuthenticateKeyboardInteractive(sess, auth); + break; + case VIR_NET_SSH_AUTH_AGENT: + if (strstr(auth_list, "publickey")) + ret = virNetSSHAuthenticateAgent(sess, auth); + break; + case VIR_NET_SSH_AUTH_PRIVKEY: + if (strstr(auth_list, "publickey")) + ret = virNetSSHAuthenticatePrivkey(sess, auth); + break; + case VIR_NET_SSH_AUTH_PASSWORD: + if (strstr(auth_list, "password")) + ret = virNetSSHAuthenticatePassword(sess, auth); + break; + } + + /* return on success or error */ + if (ret <= 0) + return ret; + + /* the authentication method is not supported */ + if (ret == 2) + no_method = true; + + /* authentication with this method has failed */ + if (ret == 1) + auth_failed = true; + } + + if (sess->nauths == 0) { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("No authentication methods supplied")); + } else if (sess->nauths == 1) { + /* pass through the error */ + } 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 +virNetSSHOpenChannel(virNetSSHSessionPtr sess) +{ + char *errmsg; + + sess->channel = libssh2_channel_open_session(sess->session); + if (!sess->channel) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("failed to open ssh channel: %s"), + errmsg); + return -1; + } + + if (libssh2_channel_exec(sess->channel, sess->channelCommand) != 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("failed to execute command '%s': %s"), + sess->channelCommand, + errmsg); + return -1; + } + + /* nonblocking mode - currently does nothing*/ + libssh2_channel_set_blocking(sess->channel, 0); + + /* channel open */ + return 0; +} + +/* validate if all required parameters are configured */ +static int +virNetSSHValidateConfig(virNetSSHSessionPtr sess) +{ + if (sess->nauths == 0) { + virReportError(VIR_ERR_SSH, "%s", + _("No authentication methods and credentials " + "provided")); + return -1; + } + + if (!sess->channelCommand) { + virReportError(VIR_ERR_SSH, "%s", + _("No channel command provided")); + return -1; + } + + if (sess->hostKeyVerify != VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE) { + if (!sess->hostname) { + virReportError(VIR_ERR_SSH, "%s", + _("Hostname is needed for host key verification")); + return -1; + } + } + + /* everything ok */ + return 0; +} + +/* ### PUBLIC API ### */ +int +virNetSSHSessionAuthSetCallback(virNetSSHSessionPtr sess, + virConnectAuthPtr auth) +{ + virMutexLock(&sess->lock); + sess->cred = auth; + virMutexUnlock(&sess->lock); + return 0; +} + +void +virNetSSHSessionAuthReset(virNetSSHSessionPtr sess) +{ + virMutexLock(&sess->lock); + virNetSSHSessionAuthMethodsFree(sess); + virMutexUnlock(&sess->lock); +} + +int +virNetSSHSessionAuthAddPasswordAuth(virNetSSHSessionPtr sess, + const char *username, + const char *password) +{ + virNetSSHAuthMethodPtr auth; + char *user = NULL; + char *pass = NULL; + + if (!username || !password) { + virReportError(VIR_ERR_SSH, "%s", + _("Username and password must be provided " + "for password authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username)) || + !(pass = strdup(password))) + goto no_memory; + + if (!(auth = virNetSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->password = pass; + auth->method = VIR_NET_SSH_AUTH_PASSWORD; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + VIR_FREE(pass); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetSSHSessionAuthAddAgentAuth(virNetSSHSessionPtr sess, + const char *username) +{ + virNetSSHAuthMethodPtr auth; + char *user = NULL; + + if (!username) { + virReportError(VIR_ERR_SSH, "%s", + _("Username must be provided " + "for ssh agent authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username))) + goto no_memory; + + if (!(auth = virNetSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->method = VIR_NET_SSH_AUTH_AGENT; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetSSHSessionAuthAddPrivKeyAuth(virNetSSHSessionPtr sess, + const char *username, + const char *keyfile, + const char *password) +{ + virNetSSHAuthMethodPtr auth; + + char *user = NULL; + char *pass = NULL; + char *file = NULL; + + if (!username || !keyfile) { + virReportError(VIR_ERR_SSH, "%s", + _("Username and key file path must be provided " + "for private key authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username)) || + !(file = strdup(keyfile))) + goto no_memory; + + if (password && !(pass = strdup(password))) + goto no_memory; + + if (!(auth = virNetSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->password = pass; + auth->filename = file; + auth->method = VIR_NET_SSH_AUTH_PRIVKEY; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + VIR_FREE(pass); + VIR_FREE(file); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetSSHSessionAuthAddKeyboardAuth(virNetSSHSessionPtr sess, + const char *username, + int tries) +{ + virNetSSHAuthMethodPtr auth; + char *user = NULL; + + if (!username) { + virReportError(VIR_ERR_SSH, "%s", + _("Username must be provided " + "for ssh agent authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username))) + goto no_memory; + + if (!(auth = virNetSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->tries = tries; + auth->method = VIR_NET_SSH_AUTH_KEYBOARD_INTERACTIVE; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + +} + +int +virNetSSHSessionSetChannelCommand(virNetSSHSessionPtr sess, + const char *command) +{ + int ret = 0; + virMutexLock(&sess->lock); + + VIR_FREE(sess->channelCommand); + + if (command && !(sess->channelCommand = strdup(command))) { + virReportOOMError(); + ret = -1; + } + + virMutexUnlock(&sess->lock); + return ret; +} + +int +virNetSSHSessionSetHostKeyVerification(virNetSSHSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + bool readonly, + virNetSSHHostkeyVerify opt) +{ + char *errmsg; + + virMutexLock(&sess->lock); + + sess->port = port; + sess->hostKeyVerify = opt; + + VIR_FREE(sess->hostname); + + if (hostname && !(sess->hostname = strdup(hostname))) + goto no_memory; + + /* load the known hosts file */ + if (hostsfile) { + if (libssh2_knownhost_readfile(sess->knownHosts, + hostsfile, + LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("unable to load knownhosts file '%s': %s"), + hostsfile, errmsg); + goto error; + } + + /* set filename only if writing to the known hosts file is requested */ + + if (!readonly) { + VIR_FREE(sess->knownHostsFile); + if (!(sess->knownHostsFile = strdup(hostsfile))) + goto no_memory; + } + } + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + virReportOOMError(); +error: + virMutexUnlock(&sess->lock); + return -1; +} + +/* allocate and initialize a ssh session object */ +virNetSSHSessionPtr virNetSSHSessionNew(void) +{ + virNetSSHSessionPtr sess = NULL; + + if (virNetSSHSessionInitialize() < 0) + goto error; + + if (!(sess = virObjectNew(virNetSSHSessionClass))) + goto error; + + /* initialize internal structures */ + if (virMutexInit(&sess->lock) < 0) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to initialize mutex")); + goto error; + } + + /* initialize session data, use the internal data for callbacks + * and stick to default memory management functions */ + if (!(sess->session = libssh2_session_init_ex(NULL, + NULL, + NULL, + (void *)sess))) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to initialize libssh2 session")); + goto error; + } + + if (!(sess->knownHosts = libssh2_knownhost_init(sess->session))) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to initialize libssh2 known hosts table")); + goto error; + } + + if (!(sess->agent = libssh2_agent_init(sess->session))) { + virReportError(VIR_ERR_SSH, "%s", + _("Failed to initialize libssh2 agent handle")); + goto error; + } + + VIR_DEBUG("virNetSSHSessionPtr: %p, LIBSSH2_SESSION: %p", + sess, sess->session); + + /* set blocking mode for libssh2 until handshake is complete */ + libssh2_session_set_blocking(sess->session, 1); + + /* default states for config variables */ + sess->state = VIR_NET_SSH_STATE_NEW; + sess->hostKeyVerify = VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE; + + return sess; + +error: + virObjectUnref(sess); + return NULL; +} + +int +virNetSSHSessionConnect(virNetSSHSessionPtr sess, + int sock) +{ + int ret; + char *errmsg; + + VIR_DEBUG("sess=%p, sock=%d", sess, sock); + + if (!sess || sess->state != VIR_NET_SSH_STATE_NEW) { + virReportError(VIR_ERR_SSH, "%s", + _("Invalid virNetSSHSessionPtr")); + return -1; + } + + virMutexLock(&sess->lock); + + /* check if configuration is valid */ + if ((ret = virNetSSHValidateConfig(sess)) < 0) + goto error; + + /* open session */ + ret = libssh2_session_handshake(sess->session, sock); + /* libssh2 is in blocking mode, so EAGAIN will never happen */ + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_NO_CONNECT, + _("SSH session handshake failed: %s"), + errmsg); + goto error; + } + + /* verify the SSH host key */ + if ((ret = virNetSSHCheckHostKey(sess)) != 0) + goto error; + + /* authenticate */ + if ((ret = virNetSSHAuthenticate(sess)) != 0) + goto error; + + /* open channel */ + if ((ret = virNetSSHOpenChannel(sess)) != 0) + goto error; + + /* all set */ + /* switch to nonblocking mode and return */ + libssh2_session_set_blocking(sess->session, 0); + sess->state = VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE; + + virMutexUnlock(&sess->lock); + return ret; + +error: + sess->state = VIR_NET_SSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + return ret; +} + +/* do a read from a ssh channel, used instead of normal read on socket */ +ssize_t +virNetSSHChannelRead(virNetSSHSessionPtr sess, + char *buf, + size_t len) +{ + ssize_t ret = -1; + ssize_t read_n = 0; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_SSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_SSH, + _("Remote program terminated " + "with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_SSH, "%s", + _("Tried to write socket in error state")); + + virMutexUnlock(&sess->lock); + 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 = libssh2_channel_read(sess->channel, + buf + read_n, + len - read_n); + + if (ret == LIBSSH2_ERROR_EAGAIN) + goto success; + + if (ret < 0) + goto error; + + read_n += ret; + } + + /* try to read something into the internal buffer */ + if (sess->bufUsed == 0) { + ret = libssh2_channel_read(sess->channel, + sess->rbuf, + VIR_NET_SSH_BUFFER_SIZE); + + if (ret == LIBSSH2_ERROR_EAGAIN) + 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 = libssh2_channel_read_stderr(sess->channel, + sess->rbuf, + VIR_NET_SSH_BUFFER_SIZE - 1); + if (ret > 0) { + sess->rbuf[ret] = '\0'; + VIR_DEBUG("flushing stderr, data='%s'", sess->rbuf); + } + } + + if (libssh2_channel_eof(sess->channel)) { + if (libssh2_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_SSH, + _("Remote command terminated with non-zero code: %d"), + libssh2_channel_get_exit_status(sess->channel)); + sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel); + sess->state = VIR_NET_SSH_STATE_ERROR_REMOTE; + virMutexUnlock(&sess->lock); + return -1; + } + + sess->state = VIR_NET_SSH_STATE_CLOSED; + virMutexUnlock(&sess->lock); + return -1; + } + +success: + virMutexUnlock(&sess->lock); + return read_n; + +error: + sess->state = VIR_NET_SSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + return ret; +} + +ssize_t +virNetSSHChannelWrite(virNetSSHSessionPtr sess, + const char *buf, + size_t len) +{ + ssize_t ret; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_SSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_SSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_SSH, + _("Remote program terminated with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_SSH, "%s", + _("Tried to write socket in error state")); + ret = -1; + goto cleanup; + } + + if (libssh2_channel_eof(sess->channel)) { + if (libssh2_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_SSH, + _("Remote program terminated with non-zero code: %d"), + libssh2_channel_get_exit_status(sess->channel)); + sess->state = VIR_NET_SSH_STATE_ERROR_REMOTE; + sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel); + + ret = -1; + goto cleanup; + } + + sess->state = VIR_NET_SSH_STATE_CLOSED; + ret = -1; + goto cleanup; + } + + ret = libssh2_channel_write(sess->channel, buf, len); + if (ret == LIBSSH2_ERROR_EAGAIN) { + ret = 0; + goto cleanup; + } + + if (ret < 0) { + char *msg; + sess->state = VIR_NET_SSH_STATE_ERROR; + libssh2_session_last_error(sess->session, &msg, NULL, 0); + virReportError(VIR_ERR_SSH, + _("write failed: %s"), msg); + } + +cleanup: + virMutexUnlock(&sess->lock); + return ret; +} + +bool +virNetSSHSessionHasCachedData(virNetSSHSessionPtr sess) +{ + bool ret; + + if (!sess) + return false; + + virMutexLock(&sess->lock); + + ret = sess->bufUsed > 0; + + virMutexUnlock(&sess->lock); + return ret; +} diff --git a/src/rpc/virnetsshsession.h b/src/rpc/virnetsshsession.h new file mode 100644 index 0000000000..eb92e43fd2 --- /dev/null +++ b/src/rpc/virnetsshsession.h @@ -0,0 +1,83 @@ +/* + * virnetsshsession.h: ssh transport provider based on libssh2 + * + * Copyright (C) 2012 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 + * . + * + * Author: Peter Krempa + */ +#ifndef __VIR_NET_SSH_SESSION_H__ +# define __VIR_NET_SSH_SESSION_H__ + +# include "internal.h" + +typedef struct _virNetSSHSession virNetSSHSession; +typedef virNetSSHSession *virNetSSHSessionPtr; + +virNetSSHSessionPtr virNetSSHSessionNew(void); +void virNetSSHSessionFree(virNetSSHSessionPtr sess); + +typedef enum { + VIR_NET_SSH_HOSTKEY_VERIFY_NORMAL, + VIR_NET_SSH_HOSTKEY_VERIFY_AUTO_ADD, + VIR_NET_SSH_HOSTKEY_VERIFY_IGNORE +} virNetSSHHostkeyVerify; + +int virNetSSHSessionSetChannelCommand(virNetSSHSessionPtr sess, + const char *command); + +void virNetSSHSessionAuthReset(virNetSSHSessionPtr sess); + +int virNetSSHSessionAuthSetCallback(virNetSSHSessionPtr sess, + virConnectAuthPtr auth); + +int virNetSSHSessionAuthAddPasswordAuth(virNetSSHSessionPtr sess, + const char *username, + const char *password); + +int virNetSSHSessionAuthAddAgentAuth(virNetSSHSessionPtr sess, + const char *username); + +int virNetSSHSessionAuthAddPrivKeyAuth(virNetSSHSessionPtr sess, + const char *username, + const char *keyfile, + const char *password); + +int virNetSSHSessionAuthAddKeyboardAuth(virNetSSHSessionPtr sess, + const char *username, + int tries); + +int virNetSSHSessionSetHostKeyVerification(virNetSSHSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + bool readonly, + virNetSSHHostkeyVerify opt); + +int virNetSSHSessionConnect(virNetSSHSessionPtr sess, + int sock); + +ssize_t virNetSSHChannelRead(virNetSSHSessionPtr sess, + char *buf, + size_t len); + +ssize_t virNetSSHChannelWrite(virNetSSHSessionPtr sess, + const char *buf, + size_t len); + +bool virNetSSHSessionHasCachedData(virNetSSHSessionPtr sess); + +#endif /* ___VIR_NET_SSH_SESSION_H_ */ diff --git a/src/util/virterror.c b/src/util/virterror.c index e3d5de8284..3ee2ae0aa2 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -113,7 +113,9 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Authentication Utils", "DBus Utils", "Parallels Cloud Server", - "Device Config" + "Device Config", + + "SSH transport layer" /* 50 */ ) @@ -1192,6 +1194,11 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("Operation not supported: %s"); break; + case VIR_ERR_SSH: + if (info == NULL) + errmsg = _("SSH transport error"); + else + errmsg = _("SSH transport error: %s"); } return errmsg; }