This patch adds helper functions to libssh2 that enable us to use libssh2 in conjunction with libvirt-native virNetSocket-s instead of using a spawned "ssh" client process. This implemetation supports tunneled plaintext, keyboard-interactive, private key, ssh agent based and null authentication. Libvirt's Auth callback is used for interaction with the user. (Keyboar interactive authentication, adding of host keys, private key passphrases). This enables seamless integration into the application using libvirt, as no helpers as "ssh-askpass" are needed. Reading and writing of OpenSSH style "knownHosts" files is supported. Communication is done using SSH exec channel, where the user may specify arbitrary command to be executed on the remote side and reads and writes to/from stdin/out are sent through the ssh channel. Usage of stderr is not supported (by this code). As a bonus, this should (untested) add SSH support to libvirt clients running on Windows. TODO: - add code for private key authentication - add code for SSH agent based authentication - add default paths for private key and known hosts file on unix systems - ask to add key to known host, if normal mode is selected. - change some error codes and messages to something more apropriate - tests? * configure.ac: Add checking for correct version and version dependent define * po/POTFILES.in: Add to translation. * src/Makefile.am: specify paths to new files * src/rpc/virnetlibsshcontext.c: Helpers for keeping ssh session context * src/rpc/virnetlibsshcontext.h: headers for above --- configure.ac | 40 ++- po/POTFILES.in | 1 + src/Makefile.am | 9 + src/rpc/virnetlibsshcontext.c | 900 +++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetlibsshcontext.h | 76 ++++ 5 files changed, 1022 insertions(+), 4 deletions(-) create mode 100644 src/rpc/virnetlibsshcontext.c create mode 100644 src/rpc/virnetlibsshcontext.h diff --git a/configure.ac b/configure.ac index 3b7535e..955573f 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,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" dnl Checks for C compiler. @@ -305,6 +306,8 @@ AC_ARG_WITH([remote], AC_HELP_STRING([--with-remote], [add remote driver support @<:@default=yes@:>@]),[],[with_remote=yes]) AC_ARG_WITH([libvirtd], AC_HELP_STRING([--with-libvirtd], [add libvirtd support @<:@default=yes@:>@]),[],[with_libvirtd=yes]) +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 @@ -1425,29 +1428,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, [wether 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/po/POTFILES.in b/po/POTFILES.in index a3685e8..91bfb9b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -75,6 +75,7 @@ src/remote/remote_driver.c src/rpc/virnetclient.c src/rpc/virnetclientprogram.c src/rpc/virnetclientstream.c +src/rpc/virnetlibsshcontext.c src/rpc/virnetmessage.c src/rpc/virnetsaslcontext.c src/rpc/virnetsocket.c diff --git a/src/Makefile.am b/src/Makefile.am index e931d41..22bc738 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1379,6 +1379,13 @@ libvirt_net_rpc_la_SOURCES = \ rpc/virnetprotocol.h rpc/virnetprotocol.c \ rpc/virnetsocket.h rpc/virnetsocket.c \ rpc/virnettlscontext.h rpc/virnettlscontext.c +if HAVE_LIBSSH2 +libvirt_net_rpc_la_SOURCES += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +else +EXTRA_DIST += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +endif if HAVE_SASL libvirt_net_rpc_la_SOURCES += \ rpc/virnetsaslcontext.h rpc/virnetsaslcontext.c @@ -1389,11 +1396,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/rpc/virnetlibsshcontext.c b/src/rpc/virnetlibsshcontext.c new file mode 100644 index 0000000..848d5fc --- /dev/null +++ b/src/rpc/virnetlibsshcontext.c @@ -0,0 +1,900 @@ +/* + * virnetlibsshcontext.c: libssh network transport provider + * + * Copyright (C) 2011 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Peter Krempa <pkrempa@xxxxxxxxxx> + */ + +#include <config.h> +#include <libssh2.h> +#include <libssh2_publickey.h> + +#include "virnetlibsshcontext.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" + +#define VIR_FROM_THIS VIR_FROM_RPC +#define virNetError(code, ...) \ + virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +#define VIR_NET_LIBSSH_DEFAULT_KNOWN_HOSTS_PATH NULL + +static const char vir_libssh2_key_comment[] = "added by libvirt libssh transport"; +#define VIR_NET_LIBSSH_BUFFER_SIZE 200 + +typedef enum { + VIR_NET_LIBSSH_STATE_NEW, + VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE, + VIR_NET_LIBSSH_STATE_AUTH_CALLBACK_ERROR, + VIR_NET_LIBSSH_STATE_CLOSED, + VIR_NET_LIBSSH_STATE_ERROR +} virNetLibSSHSessionState; + +typedef enum { + VIR_NET_LIBSSH_AUTHCB_OK, + VIR_NET_LIBSSH_AUTHCB_NO_METHOD, + VIR_NET_LIBSSH_AUTHCB_OOM, + VIR_NET_LIBSSH_AUTHCB_RETR_ERR +} virNetLibSSHAuthCallbackError; + +struct _virNetLibSSHSession { + virNetLibSSHSessionState state; + virMutex lock; + + /* libssh2 internal stuff */ + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + LIBSSH2_KNOWNHOSTS *knownHosts; + LIBSSH2_AGENT*agent; + + /* for host key checking */ + virNetLibSSHHostkeyVerify hostKeyVerify; + const char *knownHostsFile; + const char *hostname; + int port; + + /* authentication stuff */ + const char *username; + const char *password; + const char *privkey; + virConnectAuthPtr cred; + virNetLibSSHAuthCallbackError authCbErr; + + /* channel stuff */ + const char *channelCommand; + + /* read cache */ + char rbuf[VIR_NET_LIBSSH_BUFFER_SIZE]; + size_t bufUsed; + size_t bufStart; +}; + +/* keyboard interactive authentication callback */ +static void virNetLibSSHKbIntCb(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) +{ + virNetLibSSHSessionPtr priv = *opaque; + virConnectCredentialPtr askcred = NULL; + int i; + int credtype_echo = -1; + int credtype_noecho = -1; + char *tmp; + + priv->authCbErr = VIR_NET_LIBSSH_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_LIBSSH_AUTHCB_NO_METHOD; + return; + } + + if (VIR_ALLOC_N(askcred, num_prompts) < 0) { + priv->authCbErr = VIR_NET_LIBSSH_AUTHCB_OOM; + return; + } + + /* fill data structures for auth callback */ + for (i = 0; i < num_prompts; i++) { + /* remove colon and trailing spaces from prompts, as default behavior + * of libvirt's auth callback is to add them */ + if ((tmp = strrchr(prompts[i].text, ':'))) + *tmp = '\0'; + + askcred[i].prompt = prompts[i].text; + 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_LIBSSH_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); + + 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, not 0 otherwise + */ +static int virNetLibSSHCheckHostKey(virNetLibSSHSessionPtr sess) +{ + int ret; + const char *key; + int keyType; + size_t keyLength; + char *errmsg; + virBuffer hostbuff = VIR_BUFFER_INITIALIZER; + virConnectCredential askKey; + + if (sess->hostKeyVerify == VIR_NET_LIBSSH_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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Failed to get ssh session 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, + NULL); + + switch (ret) { + case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: + /* key was not found, query to add it to database */ + if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL) { + /* ask to add the key */ + if (!sess->cred || !sess->cred->cb) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("No user interaction callback provided: " + "Can't verify the session host key")); + return -1; + } + +// memset(&askKey, 0, sizeof(virConnectCredential)); + + //TODO: + } + + + /* VIR_NET_LIBSSH_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: + virNetError(VIR_ERR_INTERNAL_ERROR, + _("unsupported ssh2 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(&hostbuff, "[%s]:%d", sess->hostname, sess->port); + if (virBufferError(&hostbuff) != 0) { + virReportOOMError(); + return -1; + } + + char *hostnameStr = virBufferContentAndReset(&hostbuff); + + if ((ret = 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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("unable to add ssh host key: %s"), errmsg); + } + + //VIR_FREE(hostnameStr); + + /* write the host key file - if applicable */ + if (sess->knownHostsFile) { + ret = libssh2_knownhost_writefile(sess->knownHosts, + sess->knownHostsFile, + LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to write known_host file '%s': %s"), + sess->knownHostsFile, + errmsg); + return ret; + } + } + /* key was accepted and added if requested */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MATCH: + /* host key matches */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: + /* host key verification failed */ + // TODO: error message + virNetError(VIR_ERR_INTERNAL_ERROR, + _("SSH HOST KEY VERIFICATION FAILED!!!")); + return -1; + + case LIBSSH2_KNOWNHOST_CHECK_FAILURE: + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("SSH host key check failed: %s"), + errmsg); + return -1; + + default: /* should never happen (tm) */ + virNetError(VIR_ERR_INTERNAL_ERROR, " "); + return -1; + } + + return -1; +} + +/* select auth method and authenticate */ +static int virNetLibSSHAuthenticate(virNetLibSSHSessionPtr sess) +{ + char *auth_list; + char *errmsg; + int ret; + + /* obtain list of supported auth methods */ + auth_list = libssh2_userauth_list(sess->session, + sess->username, + strlen(sess->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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("could retrieve authentication methods list: %s"), + errmsg); + return 1; + } + + /* go through supported auth methods and try the first one, that works */ + if (strstr(auth_list, "publickey")) { + /* try ssh agent authentication */ + //TODO + + /* regular public key authentication */ + if (sess->privkey) { + //TODO: + } + } + + /* tunelled password authentication */ + if (sess->password && strstr(auth_list, "password")) { + ret = libssh2_userauth_password(sess->session, + sess->username, + sess->password); + + if (ret != LIBSSH2_ERROR_AUTHENTICATION_FAILED) { + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("tunelled password authentication failed: %s"), + errmsg); + } + + /* auth success */ + return ret; + } + /* authentication has failed, try keyboard interactive */ + } + + /* keyboard interactive authentication */ + if (sess->cred && + sess->cred->cb && + strstr(auth_list, "keyboard-interactive")) { + + while (true) { + ret = libssh2_userauth_keyboard_interactive(sess->session, + sess->username, + virNetLibSSHKbIntCb); + + /* check for errors while calling the callback */ + switch (sess->authCbErr) { + case VIR_NET_LIBSSH_AUTHCB_NO_METHOD: + virNetError(VIR_ERR_INTERNAL_ERROR, + _("no suitable method to retrieve " + "authentication cretentials")); + return -1; + case VIR_NET_LIBSSH_AUTHCB_OOM: + virReportOOMError(); + return -1; + case VIR_NET_LIBSSH_AUTHCB_RETR_ERR: + virNetError(VIR_ERR_INTERNAL_ERROR, + _("failed to retrieve credentials")); + return -1; + case VIR_NET_LIBSSH_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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("keyboard interactive authentication failed: %s"), + errmsg); + return -1; + } + } + } + + virNetError(VIR_ERR_INTERNAL_ERROR, + _("No suitable authentication method found")); + + return -1; +} + +/* open channel */ +static int virNetLibSSHOpenChannel(virNetLibSSHSessionPtr sess) +{ + char *errmsg; + + sess->channel = libssh2_channel_open_session(sess->session); + if (!sess->channel) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("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 virNetLibSSHValidateConfig(const virNetLibSSHSessionPtr sess) +{ + if (!sess->username) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Username not provided")); + return -1; + } + + if (sess->hostKeyVerify != VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) { + if (!sess->hostname) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Hostname is needed for host key verification")); + return -1; + } + } + + /* everything ok */ + return 0; +} + +/* ### PUBLIC API ### */ +int virNetLibSSHSessionSetAuthCallback(virNetLibSSHSessionPtr sess, + virConnectAuthPtr auth) +{ + sess->cred = auth; + return 0; +} + +int virNetLibSSHSessionSetChannelCommand(virNetLibSSHSessionPtr sess, + const char *command) +{ + virMutexLock(&sess->lock); + + if (command) { + VIR_FREE(sess->channelCommand); + if ((sess->channelCommand = strdup(command)) == NULL) { + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + } + } + + virMutexUnlock(&sess->lock); + return 0; +} + +int virNetLibSSHSessionSetCredentials(virNetLibSSHSessionPtr sess, + const char *username, + const char *password) +{ + virMutexLock(&sess->lock); + + if (username) { + VIR_FREE(sess->username); + if ((sess->username = strdup(username)) == NULL) + goto oom; + } + + if (password) { + VIR_FREE(sess->password); + if ((sess->password = strdup(password)) == NULL) + goto oom; + } + + virMutexUnlock(&sess->lock); + return 0; + +oom: + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int virNetLibSSHSessionSetPrivateKey(virNetLibSSHSessionPtr sess, + const char *keyfile) +{ + virMutexLock(&sess->lock); + + if (keyfile) { + VIR_FREE(sess->privkey); + if ((sess->privkey = strdup(keyfile)) == NULL) { + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + } + } + + virMutexUnlock(&sess->lock); + return 0; +} + +int virNetLibSSHSessionSetHostKeyVerification(virNetLibSSHSessionPtr sess, + const char *hostname, + const int port, + const char *hostsfile, + bool readonly, + virNetLibSSHHostkeyVerify opt) +{ + char *errmsg; + + virMutexLock(&sess->lock); + + sess->port = port; + sess->hostKeyVerify = opt; + + if (hostname) { + VIR_FREE(sess->hostname); + if ((sess->hostname = strdup(hostname)) == NULL) { + virReportOOMError(); + goto error; + } + } + + /* 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); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("unable to load known hosts file '%s': %s"), + hostsfile, errmsg); + } + + /* set filename only if writing to the known hosts file is requested */ + + if (!readonly) { + VIR_FREE(sess->knownHostsFile); + if ((sess->knownHostsFile = strdup(hostsfile)) == NULL) { + virReportOOMError(); + goto error; + } + } + } + + virMutexUnlock(&sess->lock); + return 0; + +error: + virMutexUnlock(&sess->lock); + return -1; +} + + + +/* allocate and initialize a ssh session object */ +virNetLibSSHSessionPtr virNetLibSSHSessionNew(void) +{ + virNetLibSSHSessionPtr sess; + if (VIR_ALLOC(sess) < 0) + return NULL; + + /* initialize internal structures */ + if (virMutexInit(&sess->lock) < 0) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("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))) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Failed to initialize libssh2 session")); + goto error; + } + + if (!(sess->knownHosts = libssh2_knownhost_init(sess->session))) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Failed to initialize libssh2 known hosts table")); + goto error; + } + + if (!(sess->agent = libssh2_agent_init(sess->session))) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Failed to initialize libssh2 agent handle")); + goto error; + } + + VIR_DEBUG("virNetLibSSHSessionPtr=0x%p, LIBSSH2_SESSION *=0x%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_LIBSSH_STATE_NEW; + sess->hostKeyVerify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE; + + return sess; + +error: + libssh2_agent_free(sess->agent); + libssh2_knownhost_free(sess->knownHosts); + libssh2_session_free(sess->session); + VIR_FREE(sess); + return NULL; +} + +/* close session and free internal data */ +void virNetLibSSHSessionFree(virNetLibSSHSessionPtr sess) +{ + VIR_DEBUG("sess=0x%p", sess); + + if (!sess) + return; + + virMutexLock(&sess->lock); + + 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: virNetLibSSHSessionFree()"); + libssh2_session_free(sess->session); + } + + VIR_FREE(sess->username); + VIR_FREE(sess->password); + VIR_FREE(sess->privkey); + VIR_FREE(sess->channelCommand); + VIR_FREE(sess->hostname); + VIR_FREE(sess->knownHostsFile); + + VIR_FREE(sess); +} + +int virNetLibSSHSessionConnect(virNetLibSSHSessionPtr sess, + int sock) +{ + int ret; + char *errmsg; + + VIR_DEBUG("sess=0x%p, sock=%d", sess, sock); + + if (!sess || sess->state != VIR_NET_LIBSSH_STATE_NEW) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Invalid virNetLibSSHSessionPtr")); + return -1; + } + + virMutexLock(&sess->lock); + + /* check if configuration is valid */ + if ((ret = virNetLibSSHValidateConfig(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); + virNetError(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 */ + libssh2_session_set_blocking(sess->session, 0); + sess->state = VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE; + + virMutexUnlock(&sess->lock); + return ret; +error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + 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) +{ + ssize_t ret = -1; + ssize_t read_n = 0; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Tried to read socket in error state")); + + ret = -1; + goto error; + } + + 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_LIBSSH_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_LIBSSH_BUFFER_SIZE - 1); + if (ret > 0) { + sess->rbuf[ret] = '\0'; + VIR_DEBUG("flushing stderr, data='%s'", sess->rbuf); + } + } + + if (libssh2_channel_eof(sess->channel)) { + sess->state = VIR_NET_LIBSSH_STATE_CLOSED; + virMutexUnlock(&sess->lock); + return -1; + } + +success: + virMutexUnlock(&sess->lock); + return read_n; + +error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + return ret; +} + +ssize_t +virNetLibSSHChannelWrite(virNetLibSSHSessionPtr sess, + const char *buf, + size_t len) +{ + ssize_t ret; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + virNetError(VIR_ERR_INTERNAL_ERROR, + _("Tried to read socket in error state")); + + ret = -1; + goto cleanup; + } + + if (libssh2_channel_eof(sess->channel)) { + sess->state = VIR_NET_LIBSSH_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_LIBSSH_STATE_ERROR; + libssh2_session_last_error(sess->session, &msg, NULL, 0); + virNetError(VIR_ERR_INTERNAL_ERROR, + _("write failed: %s"), msg); + } + +cleanup: + virMutexUnlock(&sess->lock); + return ret; +} + +bool virNetLibSSHHasCachedData(virNetLibSSHSessionPtr sess) +{ + bool ret; + + if (!sess) + return false; + + virMutexLock(&sess->lock); + + ret = sess->bufUsed > 0; + + virMutexUnlock(&sess->lock); + return ret; +} diff --git a/src/rpc/virnetlibsshcontext.h b/src/rpc/virnetlibsshcontext.h new file mode 100644 index 0000000..98a022c --- /dev/null +++ b/src/rpc/virnetlibsshcontext.h @@ -0,0 +1,76 @@ +/* + * virnetlibsshcontext.h: libssh transport provider + * + * Copyright (C) 2011 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Peter Krempa <pkrempa@xxxxxxxxxx> + */ +#ifndef __VIR_NET_LIBSSH_CONTEXT_H__ +# define __VIR_NET_LIBSSH_CONTEXT_H__ + +# include "internal.h" + +typedef struct _virNetLibSSHSession virNetLibSSHSession; +typedef virNetLibSSHSession *virNetLibSSHSessionPtr; + +virNetLibSSHSessionPtr virNetLibSSHSessionNew(void); +void virNetLibSSHSessionFree(virNetLibSSHSessionPtr sess); + +typedef enum { + VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL, + VIR_NET_LIBSSH_HOSTKEY_VERIFY_AUTO_ADD, + VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE +} virNetLibSSHHostkeyVerify; + +int virNetLibSSHSessionSetAuthCallback(virNetLibSSHSessionPtr sess, + virConnectAuthPtr auth); + +int virNetLibSSHSessionSetChannelCommand(virNetLibSSHSessionPtr sess, + const char *command); + +int virNetLibSSHSessionSetCredentials(virNetLibSSHSessionPtr sess, + const char *username, + const char *password); + +int virNetLibSSHSessionSetPrivateKey(virNetLibSSHSessionPtr sess, + const char *keyfile); + +int virNetLibSSHSessionSetHostKeyVerification(virNetLibSSHSessionPtr sess, + const char *hostname, + const int port, + const char *hostsfile, + bool readonly, + virNetLibSSHHostkeyVerify opt); + +int virNetLibSSHSessionConnect(virNetLibSSHSessionPtr sess, + int sock); + +ssize_t +virNetLibSSHChannelRead(virNetLibSSHSessionPtr sess, + char *buf, + size_t len); + +ssize_t +virNetLibSSHChannelWrite(virNetLibSSHSessionPtr sess, + const char *buf, + size_t len); + +bool +virNetLibSSHHasCachedData(virNetLibSSHSessionPtr sess); + + +#endif /* __VIR_NET_LIBSSH_CONTEXT_H__ */ -- 1.7.3.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list