diff --git a/config-post.h b/config-post.h index 6eb63db76b..090cc28319 100644 --- a/config-post.h +++ b/config-post.h @@ -36,6 +36,7 @@ # undef WITH_DTRACE_PROBES # undef WITH_GNUTLS # undef WITH_GNUTLS_GCRYPT +# undef WITH_LIBSSH # undef WITH_MACVTAP # undef WITH_NUMACTL # undef WITH_SASL @@ -60,6 +61,7 @@ # undef WITH_DTRACE_PROBES # undef WITH_GNUTLS # undef WITH_GNUTLS_GCRYPT +# undef WITH_LIBSSH # undef WITH_MACVTAP # undef WITH_NUMACTL # undef WITH_SASL diff --git a/configure.ac b/configure.ac index 3ff4c42a8f..66d88c430c 100644 --- a/configure.ac +++ b/configure.ac @@ -217,6 +217,7 @@ if test "$with_remote" = "no" ; then with_gnutls=no with_ssh2=no with_sasl=no + with_libssh=no fi # Stateful drivers are useful only when building the daemon. if test "$with_libvirtd" = "no" ; then @@ -246,6 +247,7 @@ LIBVIRT_CHECK_DBUS LIBVIRT_CHECK_FUSE LIBVIRT_CHECK_GLUSTER LIBVIRT_CHECK_HAL +LIBVIRT_CHECK_LIBSSH LIBVIRT_CHECK_NETCF LIBVIRT_CHECK_NUMACTL LIBVIRT_CHECK_OPENWSMAN @@ -2665,6 +2667,7 @@ LIBVIRT_RESULT_DBUS LIBVIRT_RESULT_FUSE LIBVIRT_RESULT_GLUSTER LIBVIRT_RESULT_HAL +LIBVIRT_RESULT_LIBSSH LIBVIRT_RESULT_NETCF LIBVIRT_RESULT_NUMACTL LIBVIRT_RESULT_OPENWSMAN diff --git a/m4/virt-libssh.m4 b/m4/virt-libssh.m4 new file mode 100644 index 0000000000..88ece21b26 --- /dev/null +++ b/m4/virt-libssh.m4 @@ -0,0 +1,26 @@ +dnl The libssh.so library +dnl +dnl Copyright (C) 2016 Red Hat, Inc. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl . +dnl + +AC_DEFUN([LIBVIRT_CHECK_LIBSSH],[ + LIBVIRT_CHECK_PKG([LIBSSH], [libssh], [0.7]) +]) + +AC_DEFUN([LIBVIRT_RESULT_LIBSSH],[ + LIBVIRT_RESULT_LIB([LIBSSH]) +]) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5a370833a9..25867aef83 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -145,6 +145,7 @@ src/rpc/virnetclient.c src/rpc/virnetclientprogram.c src/rpc/virnetclientstream.c src/rpc/virnetdaemon.c +src/rpc/virnetlibsshsession.c src/rpc/virnetmessage.c src/rpc/virnetsaslcontext.c src/rpc/virnetserver.c diff --git a/src/Makefile.am b/src/Makefile.am index d417b6eb41..aaba9e6cc6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2127,6 +2127,12 @@ else ! WITH_ATOMIC_OPS_PTHREAD SYM_FILES += $(srcdir)/libvirt_atomic.syms endif ! WITH_ATOMIC_OPS_PTHREAD +if WITH_LIBSSH +USED_SYM_FILES += $(srcdir)/libvirt_libssh.syms +else ! WITH_LIBSSH +SYM_FILES += $(srcdir)/libvirt_libssh.syms +endif ! WITH_LIBSSH + EXTRA_DIST += \ libvirt_public.syms \ libvirt_lxc.syms \ @@ -2204,7 +2210,8 @@ libvirt_admin_la_CFLAGS += \ $(YAJL_CFLAGS) \ $(SSH2_CFLAGS) \ $(SASL_CFLAGS) \ - $(GNUTLS_CFLAGS) + $(GNUTLS_CFLAGS) \ + $(LIBSSH_CFLAGS) libvirt_admin_la_LIBADD += \ $(CAPNG_LIBS) \ @@ -2213,7 +2220,8 @@ libvirt_admin_la_LIBADD += \ $(LIBXML_LIBS) \ $(SSH2_LIBS) \ $(SASL_LIBS) \ - $(GNUTLS_LIBS) + $(GNUTLS_LIBS) \ + $(LIBSSH_LIBS) ADMIN_SYM_FILES = $(srcdir)/libvirt_admin_private.syms @@ -2790,16 +2798,25 @@ else ! WITH_SASL EXTRA_DIST += \ rpc/virnetsaslcontext.h rpc/virnetsaslcontext.c endif ! WITH_SASL +if WITH_LIBSSH +libvirt_net_rpc_la_SOURCES += \ + rpc/virnetlibsshsession.h rpc/virnetlibsshsession.c +else ! WITH_LIBSSH +EXTRA_DIST += \ + rpc/virnetlibsshsession.h rpc/virnetlibsshsession.c +endif ! WITH_LIBSSH libvirt_net_rpc_la_CFLAGS = \ $(GNUTLS_CFLAGS) \ $(SASL_CFLAGS) \ $(SSH2_CFLAGS) \ + $(LIBSSH_CFLAGS) \ $(XDR_CFLAGS) \ $(AM_CFLAGS) libvirt_net_rpc_la_LDFLAGS = \ $(GNUTLS_LIBS) \ $(SASL_LIBS) \ $(SSH2_LIBS)\ + $(LIBSSH_LIBS) \ $(SECDRIVER_LIBS) \ $(AM_LDFLAGS) \ $(NULL) diff --git a/src/libvirt_libssh.syms b/src/libvirt_libssh.syms new file mode 100644 index 0000000000..32eb44f59b --- /dev/null +++ b/src/libvirt_libssh.syms @@ -0,0 +1,21 @@ +# +# libssh session - specific symbols +# + +# rpc/virnetlibsshsession.h +virNetLibsshChannelRead; +virNetLibsshChannelWrite; +virNetLibsshSessionAuthAddAgentAuth; +virNetLibsshSessionAuthAddKeyboardAuth; +virNetLibsshSessionAuthAddPasswordAuth; +virNetLibsshSessionAuthAddPrivKeyAuth; +virNetLibsshSessionAuthSetCallback; +virNetLibsshSessionConnect; +virNetLibsshSessionHasCachedData; +virNetLibsshSessionSetChannelCommand; +virNetLibsshSessionSetHostKeyVerification; + +# Let emacs know we want case-insensitive sorting +# Local Variables: +# sort-fold-case: t +# End: diff --git a/src/rpc/virnetlibsshsession.c b/src/rpc/virnetlibsshsession.c new file mode 100644 index 0000000000..5de6629d70 --- /dev/null +++ b/src/rpc/virnetlibsshsession.c @@ -0,0 +1,1492 @@ +/* + * virnetlibsshsession.c: ssh network transport provider based on libssh + * + * Copyright (C) 2012-2016 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Author: Peter Krempa + * Author: Pino Toscano + */ +#include +#include + +#include "virnetlibsshsession.h" + +#include "internal.h" +#include "viralloc.h" +#include "virlog.h" +#include "configmake.h" +#include "virutil.h" +#include "virerror.h" +#include "virobject.h" +#include "virstring.h" +#include "virauth.h" +#include "virbuffer.h" + +#define VIR_FROM_THIS VIR_FROM_LIBSSH + +VIR_LOG_INIT("rpc.netlibsshsession"); + +#define VIR_NET_LIBSSH_BUFFER_SIZE 1024 + +/* TRACE_LIBSSH= enables tracing in libssh itself. + * The meaning of is described here: + * http://api.libssh.org/master/group__libssh__log.html + * + * The LIBVIRT_LIBSSH_DEBUG environment variable can be used + * to set/override the level of libssh debug. + */ +#define TRACE_LIBSSH 0 + +typedef enum { + VIR_NET_LIBSSH_STATE_NEW, + VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE, + VIR_NET_LIBSSH_STATE_CLOSED, + VIR_NET_LIBSSH_STATE_ERROR, + VIR_NET_LIBSSH_STATE_ERROR_REMOTE, +} virNetLibsshSessionState; + +typedef enum { + VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE, + VIR_NET_LIBSSH_AUTH_PASSWORD, + VIR_NET_LIBSSH_AUTH_PRIVKEY, + VIR_NET_LIBSSH_AUTH_AGENT +} virNetLibsshAuthMethods; + + +typedef struct _virNetLibsshAuthMethod virNetLibsshAuthMethod; +typedef virNetLibsshAuthMethod *virNetLibsshAuthMethodPtr; + +struct _virNetLibsshAuthMethod { + virNetLibsshAuthMethods method; + int ssh_flags; /* SSH_AUTH_METHOD_* for this auth method */ + + char *password; + char *filename; + + int tries; +}; + +struct _virNetLibsshSession { + virObjectLockable parent; + virNetLibsshSessionState state; + + /* libssh internal stuff */ + ssh_session session; + ssh_channel channel; + + /* for host key checking */ + virNetLibsshHostkeyVerify hostKeyVerify; + char *knownHostsFile; + char *hostname; + int port; + + /* authentication stuff */ + char *username; + virConnectAuthPtr cred; + char *authPath; + size_t nauths; + virNetLibsshAuthMethodPtr *auths; + + /* channel stuff */ + char *channelCommand; + int channelCommandReturnValue; + + /* read cache */ + char rbuf[VIR_NET_LIBSSH_BUFFER_SIZE]; + size_t bufUsed; + size_t bufStart; +}; + +static void +virNetLibsshSessionAuthMethodsFree(virNetLibsshSessionPtr sess) +{ + size_t i; + + for (i = 0; i < sess->nauths; i++) { + VIR_DISPOSE_STRING(sess->auths[i]->password); + VIR_FREE(sess->auths[i]->filename); + VIR_FREE(sess->auths[i]); + } + + VIR_FREE(sess->auths); + sess->nauths = 0; +} + +static void +virNetLibsshSessionDispose(void *obj) +{ + virNetLibsshSessionPtr sess = obj; + VIR_DEBUG("sess=0x%p", sess); + + if (!sess) + return; + + if (sess->channel) { + ssh_channel_send_eof(sess->channel); + ssh_channel_close(sess->channel); + ssh_channel_free(sess->channel); + } + + if (sess->session) { + ssh_disconnect(sess->session); + ssh_free(sess->session); + } + + virNetLibsshSessionAuthMethodsFree(sess); + + VIR_FREE(sess->channelCommand); + VIR_FREE(sess->hostname); + VIR_FREE(sess->knownHostsFile); + VIR_FREE(sess->authPath); + VIR_FREE(sess->username); +} + +static virClassPtr virNetLibsshSessionClass; +static int +virNetLibsshSessionOnceInit(void) +{ + const char *dbgLevelStr; + + if (!(virNetLibsshSessionClass = virClassNew(virClassForObjectLockable(), + "virNetLibsshSession", + sizeof(virNetLibsshSession), + virNetLibsshSessionDispose))) + return -1; + + if (ssh_init() < 0) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to initialize libssh")); + return -1; + } + +#if TRACE_LIBSSH != 0 + ssh_set_log_level(TRACE_LIBSSH); +#endif + + dbgLevelStr = virGetEnvAllowSUID("LIBVIRT_LIBSSH_DEBUG"); + if (dbgLevelStr) { + int dbgLevel = virParseNumber(&dbgLevelStr); + ssh_set_log_level(dbgLevel); + } + + return 0; +} +VIR_ONCE_GLOBAL_INIT(virNetLibsshSession); + +static virNetLibsshAuthMethodPtr +virNetLibsshSessionAuthMethodNew(virNetLibsshSessionPtr sess) +{ + virNetLibsshAuthMethodPtr auth; + + if (VIR_ALLOC(auth) < 0) + goto error; + + if (VIR_EXPAND_N(sess->auths, sess->nauths, 1) < 0) + goto error; + + sess->auths[sess->nauths - 1] = auth; + + return auth; + + error: + VIR_FREE(auth); + return NULL; +} + +/* string representation of public key of remote server */ +static char * +virLibsshServerKeyAsString(virNetLibsshSessionPtr sess) +{ + int ret; + ssh_key key; + unsigned char *keyhash; + size_t keyhashlen; + char *str; + + if (ssh_get_publickey(sess->session, &key) != SSH_OK) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to get the key of the current " + "session")); + return NULL; + } + + /* calculate remote key hash, using SHA1 algorithm that is + * usual in OpenSSH. The returned value must be freed */ + ret = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA1, + &keyhash, &keyhashlen); + ssh_key_free(key); + if (ret < 0) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to calculate ssh host key hash")); + return NULL; + } + /* format the host key into a nice userfriendly string. */ + str = ssh_get_hexa(keyhash, keyhashlen); + ssh_clean_pubkey_hash(&keyhash); + + return str; +} + +static int +virCredTypeForPrompt(virConnectAuthPtr cred, char echo) +{ + size_t i; + + for (i = 0; i < cred->ncredtype; ++i) { + int type = cred->credtype[i]; + if (echo) { + if (type == VIR_CRED_ECHOPROMPT) + return type; + } else { + if (type == VIR_CRED_PASSPHRASE || + type == VIR_CRED_NOECHOPROMPT) { + return type; + } + } + } + + return -1; +} + +static int +virLengthForPromptString(const char *str) +{ + int len = strlen(str); + + while (len > 0 && (str[len-1] == ' ' || str[len-1] == ':')) + --len; + + return len; +} + +/* check session host keys + * + * this function checks the known host database and verifies the key + * errors are raised in this func + * + * return value: 0 on success, -1 on error + */ +static int +virNetLibsshCheckHostKey(virNetLibsshSessionPtr sess) +{ + int state; + char *keyhashstr; + const char *errmsg; + + if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) + return 0; + + state = ssh_is_server_known(sess->session); + + switch (state) { + case SSH_SERVER_KNOWN_OK: + /* host key matches */ + return 0; + + case SSH_SERVER_FOUND_OTHER: + case SSH_SERVER_KNOWN_CHANGED: + keyhashstr = virLibsshServerKeyAsString(sess); + if (!keyhashstr) + return -1; + + /* host key verification failed */ + virReportError(VIR_ERR_AUTH_FAILED, + _("!!! SSH HOST KEY VERIFICATION FAILED !!!: " + "Identity of host '%s:%d' differs from stored identity. " + "Please verify the new host key '%s' to avoid possible " + "man in the middle attack. The key is stored in '%s'."), + sess->hostname, sess->port, + keyhashstr, sess->knownHostsFile); + + ssh_string_free_char(keyhashstr); + return -1; + + case SSH_SERVER_FILE_NOT_FOUND: + case SSH_SERVER_NOT_KNOWN: + /* key was not found, query to add it to database */ + if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL) { + virConnectCredential askKey; + int cred_type; + char *tmp; + + /* ask to add the key */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("No user interaction callback provided: " + "Can't verify the session host key")); + return -1; + } + + cred_type = virCredTypeForPrompt(sess->cred, 1 /* echo */); + if (cred_type == -1) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("no suitable callback for host key " + "verification")); + return -1; + } + + /* prepare data for the callback */ + memset(&askKey, 0, sizeof(virConnectCredential)); + askKey.type = cred_type; + + keyhashstr = virLibsshServerKeyAsString(sess); + if (!keyhashstr) + return -1; + + if (virAsprintf(&tmp, + _("Accept SSH host key with hash '%s' for " + "host '%s:%d' (%s/%s)?"), + keyhashstr, + sess->hostname, sess->port, + "y", "n") < 0) { + ssh_string_free_char(keyhashstr); + return -1; + } + askKey.prompt = tmp; + + if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to retrieve decision to accept " + "host key")); + VIR_FREE(tmp); + ssh_string_free_char(keyhashstr); + return -1; + } + + VIR_FREE(tmp); + + if (!askKey.result || + STRCASENEQ(askKey.result, "y")) { + virReportError(VIR_ERR_LIBSSH, + _("SSH host key for '%s' (%s) was not accepted"), + sess->hostname, keyhashstr); + ssh_string_free_char(keyhashstr); + VIR_FREE(askKey.result); + return -1; + } + ssh_string_free_char(keyhashstr); + VIR_FREE(askKey.result); + } + + /* write the host key file */ + if (ssh_write_knownhost(sess->session) < 0) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to write known_host file '%s': %s"), + sess->knownHostsFile, + errmsg); + return -1; + } + /* key was accepted and added */ + return 0; + + case SSH_SERVER_ERROR: + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to validate SSH host key: %s"), + errmsg); + return -1; + + default: /* should never happen (tm) */ + virReportError(VIR_ERR_LIBSSH, "%s", + _("Unknown state of the remote server SSH key")); + return -1; + } + + return -1; +} + +/* callback for ssh_pki_import_privkey_file, used to get the passphrase + * of a private key + */ +static int +virNetLibsshAuthenticatePrivkeyCb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify ATTRIBUTE_UNUSED, + void *userdata) +{ + virNetLibsshSessionPtr sess = userdata; + virConnectCredential retr_passphrase; + int cred_type; + char *actual_prompt = NULL; + char *p; + + /* request user's key password */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("No user interaction callback provided: " + "Can't retrieve private key passphrase")); + return -1; + } + + cred_type = virCredTypeForPrompt(sess->cred, echo); + if (cred_type == -1) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("no suitable callback for input of key passphrase")); + goto error; + } + + if (VIR_STRNDUP(actual_prompt, prompt, + virLengthForPromptString(prompt)) < 0) + goto error; + + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); + retr_passphrase.type = cred_type; + retr_passphrase.prompt = actual_prompt; + + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to retrieve private key passphrase: " + "callback has failed")); + goto error; + } + + p = virStrncpy(buf, retr_passphrase.result, + retr_passphrase.resultlen, len); + VIR_DISPOSE_STRING(retr_passphrase.result); + if (!p) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("passphrase is too long for the buffer")); + goto error; + } + + VIR_FREE(actual_prompt); + + return 0; + + error: + VIR_FREE(actual_prompt); + return -1; +} + +static int +virNetLibsshImportPrivkey(virNetLibsshSessionPtr sess, + virNetLibsshAuthMethodPtr priv, + ssh_key *ret_key) +{ + int err; + int ret; + ssh_key key; + + /* try open the key with the password set, first; since it can + * fail with SSH_ERROR also without the callback being called, + * reset the error so it is possible to check whether the callback + * failed or libssh did. + */ + virResetLastError(); + ret = ssh_pki_import_privkey_file(priv->filename, priv->password, + virNetLibsshAuthenticatePrivkeyCb, + sess, &key); + if (ret == SSH_EOF) { + virReportError(VIR_ERR_AUTH_FAILED, + _("error while reading private key '%s'"), + priv->filename); + err = SSH_AUTH_ERROR; + goto error; + } else if (ret == SSH_ERROR) { + virErrorPtr vir_err = virGetLastError(); + + if (!vir_err || !vir_err->code) { + virReportError(VIR_ERR_AUTH_FAILED, + _("error while opening private key '%s', wrong " + "passphrase?"), + priv->filename); + } + err = SSH_AUTH_ERROR; + goto error; + } + + *ret_key = key; + return SSH_AUTH_SUCCESS; + + error: + return err; +} + + +/* perform private key authentication + * + * returns SSH_AUTH_* values + */ +static int +virNetLibsshAuthenticatePrivkey(virNetLibsshSessionPtr sess, + virNetLibsshAuthMethodPtr priv) +{ + int err; + int ret; + char *tmp = NULL; + ssh_key public_key = NULL; + ssh_key private_key = NULL; + + VIR_DEBUG("sess=%p", sess); + + if (virAsprintf(&tmp, "%s.pub", priv->filename) < 0) { + err = SSH_AUTH_ERROR; + goto error; + } + + /* try to open the public part of the private key */ + ret = ssh_pki_import_pubkey_file(tmp, &public_key); + if (ret == SSH_ERROR) { + virReportError(VIR_ERR_AUTH_FAILED, + _("error while reading public key '%s'"), + tmp); + err = SSH_AUTH_ERROR; + goto error; + } else if (ret == SSH_EOF) { + /* load the private key */ + err = virNetLibsshImportPrivkey(sess, priv, &private_key); + if (err != SSH_AUTH_SUCCESS) + goto error; + + /* create the public key from the private key */ + ret = ssh_pki_export_privkey_to_pubkey(private_key, &public_key); + if (ret == SSH_ERROR) { + virReportError(VIR_ERR_AUTH_FAILED, + _("cannot export the public key from the " + "private key '%s'"), + priv->filename); + err = SSH_AUTH_ERROR; + goto error; + } + } + + VIR_FREE(tmp); + + ret = ssh_userauth_try_publickey(sess->session, NULL, public_key); + if (ret != SSH_AUTH_SUCCESS) { + err = SSH_AUTH_DENIED; + goto error; + } + + /* load the private key, if it was not loaded yet */ + if (private_key == NULL) { + err = virNetLibsshImportPrivkey(sess, priv, &private_key); + if (err != SSH_AUTH_SUCCESS) + goto error; + } + + ret = ssh_userauth_publickey(sess->session, NULL, private_key); + if (ret != SSH_AUTH_SUCCESS) { + err = SSH_AUTH_DENIED; + goto error; + } + + ssh_key_free(private_key); + ssh_key_free(public_key); + + return SSH_AUTH_SUCCESS; + + error: + if (private_key) + ssh_key_free(private_key); + if (public_key) + ssh_key_free(public_key); + VIR_FREE(tmp); + return err; +} + + +/* perform password authentication, either directly or request the password + * + * returns SSH_AUTH_* values + */ +static int +virNetLibsshAuthenticatePassword(virNetLibsshSessionPtr sess, + virNetLibsshAuthMethodPtr priv) +{ + char *password = NULL; + const char *errmsg; + int ret = -1; + + VIR_DEBUG("sess=%p", sess); + + if (priv->password) { + /* tunelled password authentication */ + if ((ret = ssh_userauth_password(sess->session, NULL, + priv->password)) == 0) { + ret = SSH_AUTH_SUCCESS; + goto cleanup; + } + } else { + /* password authentication with interactive password request */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Can't perform authentication: " + "Authentication callback not provided")); + ret = SSH_AUTH_ERROR; + goto cleanup; + } + + /* Try the authenticating the set amount of times. The server breaks the + * connection if maximum number of bad auth tries is exceeded */ + while (true) { + if (!(password = virAuthGetPasswordPath(sess->authPath, sess->cred, + "ssh", sess->username, + sess->hostname))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to retrieve password")); + ret = SSH_AUTH_ERROR; + goto cleanup; + } + + /* tunelled password authentication */ + if ((ret = ssh_userauth_password(sess->session, NULL, + password)) == 0) { + ret = SSH_AUTH_SUCCESS; + goto cleanup; + } + + VIR_DISPOSE_STRING(password); + + if (ret != SSH_AUTH_DENIED) + break; + } + } + + /* error path */ + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication failed: %s"), errmsg); + + return ret; + + cleanup: + VIR_DISPOSE_STRING(password); + return ret; +} + +/* perform keyboard interactive authentication + * + * returns SSH_AUTH_* values + */ +static int +virNetLibsshAuthenticateKeyboardInteractive(virNetLibsshSessionPtr sess, + virNetLibsshAuthMethodPtr priv) +{ + int ret; + const char *errmsg; + int try = 0; + + /* request user's key password */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("No user interaction callback provided: " + "Can't get input from keyboard interactive " + "authentication")); + return SSH_AUTH_ERROR; + } + + again: + ret = ssh_userauth_kbdint(sess->session, NULL, NULL); + while (ret == SSH_AUTH_INFO) { + const char *name, *instruction; + int nprompts, iprompt; + virBuffer buff = VIR_BUFFER_INITIALIZER; + + name = ssh_userauth_kbdint_getname(sess->session); + instruction = ssh_userauth_kbdint_getinstruction(sess->session); + nprompts = ssh_userauth_kbdint_getnprompts(sess->session); + + /* compose the main buffer with name and instruction, if present */ + if (name && name[0]) + virBufferAddStr(&buff, name); + if (instruction && instruction[0]) { + if (virBufferUse(&buff) > 0) + virBufferAddChar(&buff, '\n'); + virBufferAddStr(&buff, instruction); + } + if (virBufferUse(&buff) > 0) + virBufferAddChar(&buff, '\n'); + + if (virBufferCheckError(&buff) < 0) + return -1; + + for (iprompt = 0; iprompt < nprompts; ++iprompt) { + virConnectCredential retr_passphrase; + const char *promptStr; + int promptStrLen; + char echo; + char *prompt = NULL; + int cred_type; + + /* get the prompt */ + promptStr = ssh_userauth_kbdint_getprompt(sess->session, iprompt, + &echo); + promptStrLen = virLengthForPromptString(promptStr); + + cred_type = virCredTypeForPrompt(sess->cred, echo); + if (cred_type == -1) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("no suitable callback for input of keyboard " + "response")); + goto prompt_error; + } + + /* create the prompt for the user, using the instruction + * buffer if specified + */ + if (virBufferUse(&buff) > 0) { + virBuffer prompt_buff = VIR_BUFFER_INITIALIZER; + + virBufferAddBuffer(&prompt_buff, &buff); + virBufferAdd(&prompt_buff, promptStr, promptStrLen); + + if (virBufferCheckError(&prompt_buff) < 0) + goto prompt_error; + + prompt = virBufferContentAndReset(&prompt_buff); + } else { + if (VIR_STRNDUP(prompt, promptStr, promptStrLen) < 0) + goto prompt_error; + } + + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); + retr_passphrase.type = cred_type; + retr_passphrase.prompt = prompt; + + if (retr_passphrase.type == -1) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("no suitable callback for input of key " + "passphrase")); + goto prompt_error; + } + + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("failed to retrieve keyboard interactive " + "result: callback has failed")); + goto prompt_error; + } + + VIR_FREE(prompt); + + ret = ssh_userauth_kbdint_setanswer(sess->session, iprompt, + retr_passphrase.result); + VIR_DISPOSE_STRING(retr_passphrase.result); + if (ret < 0) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication failed: %s"), errmsg); + goto prompt_error; + } + + continue; + + prompt_error: + VIR_FREE(prompt); + virBufferFreeAndReset(&buff); + return SSH_AUTH_ERROR; + } + + virBufferFreeAndReset(&buff); + + ret = ssh_userauth_kbdint(sess->session, NULL, NULL); + ++try; + if (ret == SSH_AUTH_DENIED && (priv->tries < 0 || try < priv->tries)) + goto again; + } + + if (ret == SSH_AUTH_ERROR) { + /* error path */ + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication failed: %s"), errmsg); + } + + return ret; +} + +/* select auth method and authenticate */ +static int +virNetLibsshAuthenticate(virNetLibsshSessionPtr sess) +{ + virNetLibsshAuthMethodPtr auth; + bool no_method = false; + bool auth_failed = false; + const char *errmsg; + int ret; + int methods; + size_t i; + + VIR_DEBUG("sess=%p", sess); + + /* At this point, we can assume there is at least one + * authentication method set -- virNetLibsshValidateConfig + * already checked that. + */ + + /* try to authenticate */ + ret = ssh_userauth_none(sess->session, NULL); + if (ret == SSH_AUTH_ERROR) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("Failed to authenticate as 'none': %s"), + errmsg); + return -1; + } + + /* obtain list of supported auth methods */ + methods = ssh_userauth_list(sess->session, NULL); + + for (i = 0; i < sess->nauths; i++) { + auth = sess->auths[i]; + + if ((methods & auth->ssh_flags) == 0) { + no_method = true; + continue; + } + + ret = SSH_AUTH_DENIED; + + switch (auth->method) { + case VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE: + /* try to authenticate using the keyboard interactive way */ + ret = virNetLibsshAuthenticateKeyboardInteractive(sess, auth); + break; + case VIR_NET_LIBSSH_AUTH_AGENT: + /* try to authenticate using ssh-agent */ + ret = ssh_userauth_agent(sess->session, NULL); + if (ret == SSH_AUTH_ERROR) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to authenticate using agent: %s"), + errmsg); + } + break; + case VIR_NET_LIBSSH_AUTH_PRIVKEY: + /* try to authenticate using the provided ssh key */ + ret = virNetLibsshAuthenticatePrivkey(sess, auth); + break; + case VIR_NET_LIBSSH_AUTH_PASSWORD: + /* try to authenticate with password */ + ret = virNetLibsshAuthenticatePassword(sess, auth); + break; + } + + if (ret == SSH_AUTH_ERROR) { + /* virReportError is called already */ + return -1; + } else if (ret == SSH_AUTH_SUCCESS) { + /* authenticated */ + return 0; + } + + auth_failed = true; + } + + if (sess->nauths == 1) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to authenticate: %s"), + errmsg); + } else if (no_method && !auth_failed) { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("None of the requested authentication methods " + "are supported by the server")); + } else { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("All provided authentication methods with credentials " + "were rejected by the server")); + } + + return -1; +} + +/* open channel */ +static int +virNetLibsshOpenChannel(virNetLibsshSessionPtr sess) +{ + const char *errmsg; + + sess->channel = ssh_channel_new(sess->session); + if (!sess->channel) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to create libssh channel: %s"), + errmsg); + return -1; + } + + if (ssh_channel_open_session(sess->channel) != SSH_OK) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to open ssh channel: %s"), + errmsg); + return -1; + } + + if (ssh_channel_request_exec(sess->channel, sess->channelCommand) != SSH_OK) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("failed to execute command '%s': %s"), + sess->channelCommand, + errmsg); + return -1; + } + + /* nonblocking mode */ + ssh_channel_set_blocking(sess->channel, 0); + + /* channel open */ + return 0; +} + +/* validate if all required parameters are configured */ +static int +virNetLibsshValidateConfig(virNetLibsshSessionPtr sess) +{ + size_t i; + bool has_auths = false; + + for (i = 0; i < sess->nauths; ++i) { + if (sess->auths[i]) { + has_auths = true; + break; + } + } + if (!has_auths) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("No authentication methods and credentials " + "provided")); + return -1; + } + + if (!sess->channelCommand) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("No channel command provided")); + return -1; + } + + if (sess->hostKeyVerify != VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) { + if (!sess->hostname) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Hostname is needed for host key verification")); + return -1; + } + } + + /* everything ok */ + return 0; +} + +/* ### PUBLIC API ### */ +int +virNetLibsshSessionAuthSetCallback(virNetLibsshSessionPtr sess, + virConnectAuthPtr auth) +{ + virObjectLock(sess); + sess->cred = auth; + virObjectUnlock(sess); + return 0; +} + +int +virNetLibsshSessionAuthAddPasswordAuth(virNetLibsshSessionPtr sess, + virURIPtr uri) +{ + int ret; + virNetLibsshAuthMethodPtr auth; + + if (uri) { + VIR_FREE(sess->authPath); + + if (virAuthGetConfigFilePathURI(uri, &sess->authPath) < 0) { + ret = -1; + goto cleanup; + } + } + + virObjectLock(sess); + + if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) { + ret = -1; + goto cleanup; + } + + auth->method = VIR_NET_LIBSSH_AUTH_PASSWORD; + auth->ssh_flags = SSH_AUTH_METHOD_PASSWORD; + + ret = 0; + + cleanup: + virObjectUnlock(sess); + return ret; +} + +int +virNetLibsshSessionAuthAddAgentAuth(virNetLibsshSessionPtr sess) +{ + int ret; + virNetLibsshAuthMethodPtr auth; + + virObjectLock(sess); + + if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) { + ret = -1; + goto cleanup; + } + + auth->method = VIR_NET_LIBSSH_AUTH_AGENT; + auth->ssh_flags = SSH_AUTH_METHOD_PUBLICKEY; + + ret = 0; + + cleanup: + virObjectUnlock(sess); + return ret; +} + +int +virNetLibsshSessionAuthAddPrivKeyAuth(virNetLibsshSessionPtr sess, + const char *keyfile, + const char *password) +{ + int ret; + virNetLibsshAuthMethodPtr auth; + char *pass = NULL; + char *file = NULL; + + if (!keyfile) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Key file path must be provided " + "for private key authentication")); + ret = -1; + goto error; + } + + virObjectLock(sess); + + if (VIR_STRDUP(file, keyfile) < 0 || + VIR_STRDUP(pass, password) < 0) { + ret = -1; + goto error; + } + + if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) { + ret = -1; + goto error; + } + + auth->password = pass; + auth->filename = file; + auth->method = VIR_NET_LIBSSH_AUTH_PRIVKEY; + auth->ssh_flags = SSH_AUTH_METHOD_PUBLICKEY; + + ret = 0; + + cleanup: + virObjectUnlock(sess); + return ret; + + error: + VIR_DISPOSE_STRING(pass); + VIR_FREE(file); + goto cleanup; +} + +int +virNetLibsshSessionAuthAddKeyboardAuth(virNetLibsshSessionPtr sess, + int tries) +{ + int ret; + virNetLibsshAuthMethodPtr auth; + + virObjectLock(sess); + + if (!(auth = virNetLibsshSessionAuthMethodNew(sess))) { + ret = -1; + goto cleanup; + } + + auth->tries = tries; + auth->method = VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE; + auth->ssh_flags = SSH_AUTH_METHOD_INTERACTIVE; + + ret = 0; + + cleanup: + virObjectUnlock(sess); + return ret; + +} + +int +virNetLibsshSessionSetChannelCommand(virNetLibsshSessionPtr sess, + const char *command) +{ + int ret = 0; + virObjectLock(sess); + + VIR_FREE(sess->channelCommand); + + if (VIR_STRDUP(sess->channelCommand, command) < 0) + ret = -1; + + virObjectUnlock(sess); + return ret; +} + +int +virNetLibsshSessionSetHostKeyVerification(virNetLibsshSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + virNetLibsshHostkeyVerify opt) +{ + virObjectLock(sess); + + sess->port = port; + sess->hostKeyVerify = opt; + + VIR_FREE(sess->hostname); + + if (VIR_STRDUP(sess->hostname, hostname) < 0) + goto error; + + /* set the hostname */ + if (ssh_options_set(sess->session, SSH_OPTIONS_HOST, sess->hostname) < 0) + goto error; + + /* set the port */ + if (port > 0) { + unsigned int portU = port; + + if (ssh_options_set(sess->session, SSH_OPTIONS_PORT, &portU) < 0) + goto error; + } + + /* set the known hosts file */ + if (ssh_options_set(sess->session, SSH_OPTIONS_KNOWNHOSTS, hostsfile) < 0) + goto error; + + VIR_FREE(sess->knownHostsFile); + if (VIR_STRDUP(sess->knownHostsFile, hostsfile) < 0) + goto error; + + virObjectUnlock(sess); + return 0; + + error: + virObjectUnlock(sess); + return -1; +} + +/* allocate and initialize a libssh session object */ +virNetLibsshSessionPtr virNetLibsshSessionNew(const char *username) +{ + virNetLibsshSessionPtr sess = NULL; + + if (virNetLibsshSessionInitialize() < 0) + goto error; + + if (!(sess = virObjectLockableNew(virNetLibsshSessionClass))) + goto error; + + /* initialize session data */ + if (!(sess->session = ssh_new())) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Failed to initialize libssh session")); + goto error; + } + + if (VIR_STRDUP(sess->username, username) < 0) + goto error; + + VIR_DEBUG("virNetLibsshSessionPtr: %p, ssh_session: %p", + sess, sess->session); + + /* set blocking mode for libssh until handshake is complete */ + ssh_set_blocking(sess->session, 1); + + if (ssh_options_set(sess->session, SSH_OPTIONS_USER, sess->username) < 0) + goto error; + + /* default states for config variables */ + sess->state = VIR_NET_LIBSSH_STATE_NEW; + sess->hostKeyVerify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE; + + return sess; + + error: + virObjectUnref(sess); + return NULL; +} + +int +virNetLibsshSessionConnect(virNetLibsshSessionPtr sess, + int sock) +{ + int ret; + const char *errmsg; + + VIR_DEBUG("sess=%p, sock=%d", sess, sock); + + if (!sess || sess->state != VIR_NET_LIBSSH_STATE_NEW) { + virReportError(VIR_ERR_LIBSSH, "%s", + _("Invalid virNetLibsshSessionPtr")); + return -1; + } + + virObjectLock(sess); + + /* check if configuration is valid */ + if ((ret = virNetLibsshValidateConfig(sess)) < 0) + goto error; + + /* read ~/.ssh/config */ + if ((ret = ssh_options_parse_config(sess->session, NULL)) < 0) + goto error; + + /* set the socket FD for the libssh session */ + if ((ret = ssh_options_set(sess->session, SSH_OPTIONS_FD, &sock)) < 0) + goto error; + + /* open session */ + ret = ssh_connect(sess->session); + /* libssh is in blocking mode, so EAGAIN will never happen */ + if (ret < 0) { + errmsg = ssh_get_error(sess->session); + virReportError(VIR_ERR_NO_CONNECT, + _("SSH session handshake failed: %s"), + errmsg); + goto error; + } + + /* verify the SSH host key */ + if ((ret = virNetLibsshCheckHostKey(sess)) != 0) + goto error; + + /* authenticate */ + if ((ret = virNetLibsshAuthenticate(sess)) != 0) + goto error; + + /* open channel */ + if ((ret = virNetLibsshOpenChannel(sess)) != 0) + goto error; + + /* all set */ + /* switch to nonblocking mode and return */ + ssh_set_blocking(sess->session, 0); + sess->state = VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE; + + virObjectUnlock(sess); + return ret; + + error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virObjectUnlock(sess); + return ret; +} + +/* do a read from a ssh channel, used instead of normal read on socket */ +ssize_t +virNetLibsshChannelRead(virNetLibsshSessionPtr sess, + char *buf, + size_t len) +{ + int ret = -1; + ssize_t read_n = 0; + + virObjectLock(sess); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_LIBSSH, + _("Remote program terminated " + "with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_LIBSSH, "%s", + _("Tried to write socket in error state")); + + virObjectUnlock(sess); + return -1; + } + + if (sess->bufUsed > 0) { + /* copy the rest (or complete) internal buffer to the output buffer */ + memcpy(buf, + sess->rbuf + sess->bufStart, + len > sess->bufUsed ? sess->bufUsed : len); + + if (len >= sess->bufUsed) { + read_n = sess->bufUsed; + + sess->bufStart = 0; + sess->bufUsed = 0; + } else { + read_n = len; + sess->bufUsed -= len; + sess->bufStart += len; + + goto success; + } + } + + /* continue reading into the buffer supplied */ + if (read_n < len) { + ret = ssh_channel_read_nonblocking(sess->channel, + buf + read_n, + len - read_n, + 0); + + if (ret == SSH_EOF || (ret == 0 && ssh_channel_is_eof(sess->channel))) + goto eof; + + if (ret == SSH_AGAIN) + goto success; + + if (ret < 0) + goto error; + + read_n += ret; + } + + /* try to read something into the internal buffer */ + if (sess->bufUsed == 0) { + ret = ssh_channel_read_nonblocking(sess->channel, + sess->rbuf, + VIR_NET_LIBSSH_BUFFER_SIZE, + 0); + + if (ret == SSH_EOF || (ret == 0 && ssh_channel_is_eof(sess->channel))) + goto eof; + + if (ret == SSH_AGAIN) + goto success; + + if (ret < 0) + goto error; + + sess->bufUsed = ret; + sess->bufStart = 0; + } + + if (read_n == 0) { + /* get rid of data in stderr stream */ + ret = ssh_channel_read_nonblocking(sess->channel, + sess->rbuf, + VIR_NET_LIBSSH_BUFFER_SIZE - 1, + 1); + if (ret > 0) { + sess->rbuf[ret] = '\0'; + VIR_DEBUG("flushing stderr, data='%s'", sess->rbuf); + } + } + + if (ssh_channel_is_eof(sess->channel)) { + eof: + if (ssh_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_LIBSSH, + _("Remote command terminated with non-zero code: %d"), + ssh_channel_get_exit_status(sess->channel)); + sess->channelCommandReturnValue = ssh_channel_get_exit_status(sess->channel); + sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE; + virObjectUnlock(sess); + return -1; + } + + sess->state = VIR_NET_LIBSSH_STATE_CLOSED; + virObjectUnlock(sess); + return -1; + } + + success: + virObjectUnlock(sess); + return read_n; + + error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virObjectUnlock(sess); + return ret; +} + +ssize_t +virNetLibsshChannelWrite(virNetLibsshSessionPtr sess, + const char *buf, + size_t len) +{ + ssize_t ret; + + virObjectLock(sess); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_LIBSSH, + _("Remote program terminated with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_LIBSSH, "%s", + _("Tried to write socket in error state")); + ret = -1; + goto cleanup; + } + + if (ssh_channel_is_eof(sess->channel)) { + if (ssh_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_LIBSSH, + _("Remote program terminated with non-zero code: %d"), + ssh_channel_get_exit_status(sess->channel)); + sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE; + sess->channelCommandReturnValue = ssh_channel_get_exit_status(sess->channel); + + ret = -1; + goto cleanup; + } + + sess->state = VIR_NET_LIBSSH_STATE_CLOSED; + ret = -1; + goto cleanup; + } + + ret = ssh_channel_write(sess->channel, buf, len); + if (ret == SSH_AGAIN) { + ret = 0; + goto cleanup; + } + + if (ret < 0) { + const char *msg; + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + msg = ssh_get_error(sess->session); + virReportError(VIR_ERR_LIBSSH, + _("write failed: %s"), msg); + } + + cleanup: + virObjectUnlock(sess); + return ret; +} + +bool +virNetLibsshSessionHasCachedData(virNetLibsshSessionPtr sess) +{ + bool ret; + + if (!sess) + return false; + + virObjectLock(sess); + + ret = sess->bufUsed > 0; + + virObjectUnlock(sess); + return ret; +} diff --git a/src/rpc/virnetlibsshsession.h b/src/rpc/virnetlibsshsession.h new file mode 100644 index 0000000000..aaf2f1caae --- /dev/null +++ b/src/rpc/virnetlibsshsession.h @@ -0,0 +1,78 @@ +/* + * virnetlibsshsession.h: ssh transport provider based on libssh + * + * Copyright (C) 2012-2016 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Author: Peter Krempa + * Author: Pino Toscano + */ +#ifndef __VIR_NET_LIBSSH_SESSION_H__ +# define __VIR_NET_LIBSSH_SESSION_H__ + +# include "internal.h" +# include "viruri.h" + +typedef struct _virNetLibsshSession virNetLibsshSession; +typedef virNetLibsshSession *virNetLibsshSessionPtr; + +virNetLibsshSessionPtr virNetLibsshSessionNew(const char *username); +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 virNetLibsshSessionSetChannelCommand(virNetLibsshSessionPtr sess, + const char *command); + +int virNetLibsshSessionAuthSetCallback(virNetLibsshSessionPtr sess, + virConnectAuthPtr auth); + +int virNetLibsshSessionAuthAddPasswordAuth(virNetLibsshSessionPtr sess, + virURIPtr uri); + +int virNetLibsshSessionAuthAddAgentAuth(virNetLibsshSessionPtr sess); + +int virNetLibsshSessionAuthAddPrivKeyAuth(virNetLibsshSessionPtr sess, + const char *keyfile, + const char *password); + +int virNetLibsshSessionAuthAddKeyboardAuth(virNetLibsshSessionPtr sess, + int tries); + +int virNetLibsshSessionSetHostKeyVerification(virNetLibsshSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + 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 virNetLibsshSessionHasCachedData(virNetLibsshSessionPtr sess); + +#endif /* ___VIR_NET_LIBSSH_SESSION_H_ */