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;
}