On Mon, Oct 17, 2016 at 16:24:53 +0200, Pino Toscano wrote: > Implement a new libssh transport, which uses libssh to communicate with > remote hosts, and use it in virNetSockets. > > This new transport supports all the common ssh authentication methods, > making use of libvirt's auth callbacks for interaction with the user. > > Most of the functionalities and implementation are based on the libssh2 > transport. > --- > config-post.h | 2 + > configure.ac | 3 + > include/libvirt/virterror.h | 2 + > m4/virt-libssh.m4 | 26 + > src/Makefile.am | 21 +- > src/libvirt_libssh.syms | 22 + > src/remote/remote_driver.c | 41 ++ > src/rpc/virnetclient.c | 123 ++++ > src/rpc/virnetclient.h | 13 + > src/rpc/virnetlibsshsession.c | 1424 +++++++++++++++++++++++++++++++++++++++++ > src/rpc/virnetlibsshsession.h | 80 +++ > src/rpc/virnetsocket.c | 179 ++++++ > src/rpc/virnetsocket.h | 13 + > src/util/virerror.c | 9 +- > 14 files changed, 1955 insertions(+), 3 deletions(-) > create mode 100644 m4/virt-libssh.m4 > create mode 100644 src/libvirt_libssh.syms > create mode 100644 src/rpc/virnetlibsshsession.c > create mode 100644 src/rpc/virnetlibsshsession.h Is it possible to split out the src/rpc/virnetlibsshsession.(ch) changes (plus the ones necessary to compile it) from adding all the bits that actually make use of it? This patch is very big. > diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h > index efe83aa..2efee8f 100644 > --- a/include/libvirt/virterror.h > +++ b/include/libvirt/virterror.h > @@ -131,6 +131,7 @@ typedef enum { > VIR_FROM_XENXL = 64, /* Error from Xen xl config code */ > > VIR_FROM_PERF = 65, /* Error from perf */ > + VIR_FROM_LIBSSH = 66, /* Error from libssh connection transport */ > > # ifdef VIR_ENUM_SENTINELS > VIR_ERR_DOMAIN_LAST > @@ -317,6 +318,7 @@ typedef enum { > VIR_ERR_NO_CLIENT = 96, /* Client was not found */ > VIR_ERR_AGENT_UNSYNCED = 97, /* guest agent replies with wrong id > to guest-sync command */ > + VIR_ERR_LIBSSH = 98, /* error in libssh transport driver */ These error handling changes can be split to a separate patch as well. > } virErrorNumber; > > /** [...] > diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c > index a3cd7cd..db2bdd4 100644 > --- a/src/remote/remote_driver.c > +++ b/src/remote/remote_driver.c > @@ -673,6 +673,7 @@ remoteConnectSupportsFeatureUnlocked(virConnectPtr conn, > * - xxx:/// -> UNIX domain socket > * - xxx+ssh:/// -> SSH connection (legacy) > * - xxx+libssh2:/// -> SSH connection (using libssh2) > + * - xxx+libssh:/// -> SSH connection (using libssh) > */ > static int > doRemoteOpen(virConnectPtr conn, > @@ -689,6 +690,7 @@ doRemoteOpen(virConnectPtr conn, > trans_libssh2, > trans_ext, > trans_tcp, > + trans_libssh, > } transport; > #ifndef WIN32 > char *daemonPath = NULL; > @@ -736,6 +738,8 @@ doRemoteOpen(virConnectPtr conn, > transport = trans_ext; > } else if (STRCASEEQ(transport_str, "tcp")) { > transport = trans_tcp; > + } else if (STRCASEEQ(transport_str, "libssh")) { > + transport = trans_libssh; > } else { > virReportError(VIR_ERR_INVALID_ARG, "%s", > _("remote_open: transport in URL not recognised " > @@ -959,6 +963,43 @@ doRemoteOpen(virConnectPtr conn, > priv->is_secure = 1; > break; > > + case trans_libssh: > + if (!sockname) { > + /* Right now we don't support default session connections */ > + if (STREQ_NULLABLE(conn->uri->path, "/session")) { > + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", > + _("Connecting to session instance without " > + "socket path is not supported by the libssh " > + "connection driver")); > + goto failed; > + } > + > + if (VIR_STRDUP(sockname, > + flags & VIR_DRV_OPEN_REMOTE_RO ? > + LIBVIRTD_PRIV_UNIX_SOCKET_RO : LIBVIRTD_PRIV_UNIX_SOCKET) < 0) > + goto failed; > + } > + > + VIR_DEBUG("Starting libssh session"); > + > + priv->client = virNetClientNewLibssh(priv->hostname, > + port, > + AF_UNSPEC, > + username, > + keyfile, > + knownHosts, > + knownHostsVerify, > + sshauth, > + netcat, > + sockname, > + auth, > + conn->uri); > + if (!priv->client) > + goto failed; > + > + priv->is_secure = 1; > + break; > + > #ifndef WIN32 > case trans_unix: > if (!sockname) { All of these are actually using this as the transport so a new patch will be appropriate for those. > diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c > index 361dc1a..b296aac 100644 > --- a/src/rpc/virnetclient.c > +++ b/src/rpc/virnetclient.c > @@ -505,6 +505,129 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host, > } > #undef DEFAULT_VALUE > > +#define DEFAULT_VALUE(VAR, VAL) \ > + if (!VAR) \ > + VAR = VAL; > +virNetClientPtr virNetClientNewLibssh(const char *host, > + const char *port, > + int family, > + const char *username, > + const char *privkeyPath, > + const char *knownHostsPath, > + const char *knownHostsVerify, > + const char *authMethods, > + const char *netcatPath, > + const char *socketPath, > + virConnectAuthPtr authPtr, > + virURIPtr uri) > +{ > + virNetSocketPtr sock = NULL; > + virNetClientPtr ret = NULL; > + > + virBuffer buf = VIR_BUFFER_INITIALIZER; > + char *nc = NULL; > + char *command = NULL; > + > + char *homedir = virGetUserDirectory(); > + char *confdir = virGetUserConfigDirectory(); > + char *knownhosts = NULL; > + char *privkey = NULL; > + > + /* Use default paths for known hosts an public keys if not provided */ So is libssh able to handle e.g. ECDSA keys in known hosts? Libssh2 was not and truncated the known hosts file which was not acceptable. > + if (confdir) { > + if (!knownHostsPath) { > + if (virFileExists(confdir)) { > + virBufferAsprintf(&buf, "%s/known_hosts", confdir); > + if (!(knownhosts = virBufferContentAndReset(&buf))) Use virAsprintf instead of the two lines above. > + goto no_memory; > + } > + } else { > + if (VIR_STRDUP(knownhosts, knownHostsPath) < 0) > + goto cleanup; > + } > + } > + > + if (homedir) { > + if (!privkeyPath) { > + /* RSA */ > + virBufferAsprintf(&buf, "%s/.ssh/id_rsa", homedir); > + if (!(privkey = virBufferContentAndReset(&buf))) Same here. > + goto no_memory; > + > + if (!(virFileExists(privkey))) > + VIR_FREE(privkey); > + /* DSA */ > + if (!privkey) { > + virBufferAsprintf(&buf, "%s/.ssh/id_dsa", homedir); > + if (!(privkey = virBufferContentAndReset(&buf))) Same here. > + goto no_memory; > + > + if (!(virFileExists(privkey))) > + VIR_FREE(privkey); > + } > + } else { > + if (VIR_STRDUP(privkey, privkeyPath) < 0) > + goto cleanup; > + } > + } > + > + if (!authMethods) { > + if (privkey) > + authMethods = "agent,privkey,password,keyboard-interactive"; > + else > + authMethods = "agent,password,keyboard-interactive"; > + } > + > + DEFAULT_VALUE(host, "localhost"); > + DEFAULT_VALUE(port, "22"); > + DEFAULT_VALUE(username, "root"); > + DEFAULT_VALUE(netcatPath, "nc"); > + DEFAULT_VALUE(knownHostsVerify, "normal"); > + > + virBufferEscapeShell(&buf, netcatPath); > + if (!(nc = virBufferContentAndReset(&buf))) > + goto no_memory; > + > + virBufferAsprintf(&buf, Again, virAsprintf should be enough here. > + "sh -c " > + "'if '%s' -q 2>&1 | grep \"requires an argument\" >/dev/null 2>&1; then " > + "ARG=-q0;" > + "else " > + "ARG=;" > + "fi;" > + "'%s' $ARG -U %s'", > + nc, nc, socketPath); > + > + if (!(command = virBufferContentAndReset(&buf))) > + goto no_memory; ... and it report OOM errors directly. > + > + if (virNetSocketNewConnectLibssh(host, port, > + family, > + username, privkey, > + knownhosts, knownHostsVerify, authMethods, > + command, authPtr, uri, &sock) != 0) > + goto cleanup; > + > + if (!(ret = virNetClientNew(sock, NULL))) > + goto cleanup; > + sock = NULL; > + > + cleanup: > + VIR_FREE(command); > + VIR_FREE(privkey); > + VIR_FREE(knownhosts); > + VIR_FREE(homedir); > + VIR_FREE(confdir); > + VIR_FREE(nc); > + virObjectUnref(sock); > + return ret; > + > + no_memory: > + virReportOOMError(); > + goto cleanup; > +} > +#undef DEFAULT_VALUE > + > virNetClientPtr virNetClientNewExternal(const char **cmdargv) > { > virNetSocketPtr sock; > diff --git a/src/rpc/virnetclient.h b/src/rpc/virnetclient.h > index c772d0b..9cf3209 100644 > --- a/src/rpc/virnetclient.h > +++ b/src/rpc/virnetclient.h > @@ -67,6 +67,19 @@ virNetClientPtr virNetClientNewLibSSH2(const char *host, > virConnectAuthPtr authPtr, > virURIPtr uri); > > +virNetClientPtr virNetClientNewLibssh(const char *host, > + const char *port, > + int family, > + const char *username, > + const char *privkeyPath, > + const char *knownHostsPath, > + const char *knownHostsVerify, > + const char *authMethods, > + const char *netcatPath, > + const char *socketPath, > + virConnectAuthPtr authPtr, > + virURIPtr uri); > + > virNetClientPtr virNetClientNewExternal(const char **cmdargv); > > int virNetClientRegisterAsyncIO(virNetClientPtr client); As said, the client impl should be split from the transport impl. > diff --git a/src/rpc/virnetlibsshsession.c b/src/rpc/virnetlibsshsession.c > new file mode 100644 > index 0000000..19990ea > --- /dev/null > +++ b/src/rpc/virnetlibsshsession.c > @@ -0,0 +1,1424 @@ > +/* > + * virnetlibsshsession.c: ssh network transport provider based on libssh > + * > + * Copyright (C) 2012-2016 Red Hat, Inc. > + * > + * This library is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * This library is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with this library. If not, see > + * <http://www.gnu.org/licenses/>. > + * > + * Author: Peter Krempa <pkrempa@xxxxxxxxxx> > + * Author: Pino Toscano <ptoscano@xxxxxxxxxx> > + */ > +#include <config.h> > +#include <libssh/libssh.h> > + > +#include "virnetlibsshsession.h" > + > +#include "internal.h" > +#include "viralloc.h" > +#include "virlog.h" > +#include "configmake.h" > +#include "virutil.h" > +#include "virerror.h" > +#include "virobject.h" > +#include "virstring.h" > +#include "virauth.h" > +#include "virbuffer.h" > + > +#define VIR_FROM_THIS VIR_FROM_LIBSSH > + > +VIR_LOG_INIT("rpc.netlibsshsession"); > + > +#define VIR_NET_LIBSSH_BUFFER_SIZE 1024 > + > +/* TRACE_LIBSSH=<level> enables tracing in libssh itself. > + * The meaning of <level> is described here: > + * http://api.libssh.org/master/group__libssh__log.html > + * > + * The LIBVIRT_LIBSSH_DEBUG environment variable can be used > + * to set/override the level of libssh debug. > + */ > +#define TRACE_LIBSSH 0 > + > +typedef enum { > + VIR_NET_LIBSSH_STATE_NEW, > + VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE, > + VIR_NET_LIBSSH_STATE_CLOSED, > + VIR_NET_LIBSSH_STATE_ERROR, > + VIR_NET_LIBSSH_STATE_ERROR_REMOTE, > +} virNetLibsshSessionState; > + > +typedef enum { > + VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE, > + VIR_NET_LIBSSH_AUTH_PASSWORD, > + VIR_NET_LIBSSH_AUTH_PRIVKEY, > + VIR_NET_LIBSSH_AUTH_AGENT, > + VIR_NET_LIBSSH_AUTH_COUNT /* keep it as last, used to count */ > +} virNetLibsshAuthMethods; > + > + > +typedef struct _virNetLibsshAuthMethod virNetLibsshAuthMethod; > +typedef virNetLibsshAuthMethod *virNetLibsshAuthMethodPtr; > + > +struct _virNetLibsshAuthMethod { > + virNetLibsshAuthMethods 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; > + virNetLibsshAuthMethodPtr auths[VIR_NET_LIBSSH_AUTH_COUNT]; > + > + /* channel stuff */ > + char *channelCommand; > + int channelCommandReturnValue; > + > + /* read cache */ > + char rbuf[VIR_NET_LIBSSH_BUFFER_SIZE]; > + size_t bufUsed; > + size_t bufStart; > +}; > + > +static void > +virNetLibsshSessionAuthMethodFree(virNetLibsshAuthMethodPtr auth) > +{ > + if (!auth) > + return; > + > + VIR_FREE(auth->password); > + VIR_FREE(auth->filename); > + VIR_FREE(auth); > +} > + > +static void > +virNetLibsshSessionAuthMethodsFree(virNetLibsshSessionPtr sess) > +{ > + size_t i; > + > + for (i = 0; i < ARRAY_CARDINALITY(sess->auths); ++i) { > + virNetLibsshSessionAuthMethodFree(sess->auths[i]); > + sess->auths[i] = NULL; > + } > +} > + > +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) > + 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, > + virNetLibsshAuthMethods method) > +{ > + virNetLibsshAuthMethodPtr auth; > + > + virNetLibsshSessionAuthMethodFree(sess->auths[method]); > + if (VIR_ALLOC(auth) < 0) > + return NULL; > + > + sess->auths[method] = auth; > + auth->method = method; > + > + return auth; > +} > + > +/* string representation of public key of remote server */ > +static char* > +virSshServerKeyAsString(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 MD5 algorithm that is > + * usual in OpenSSH. The returned value must be freed */ > + ret = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_MD5, > + &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) > +{ > + unsigned int 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 = virSshServerKeyAsString(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; > + 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; > + } > + > + /* prepare data for the callback */ > + memset(&askKey, 0, sizeof(virConnectCredential)); > + askKey.type = virCredTypeForPrompt(sess->cred, 1 /* echo */); > + > + if (askKey.type == -1) { > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("no suitable callback for host key " > + "verification")); > + return -1; > + } > + > + keyhashstr = virSshServerKeyAsString(sess); > + if (!keyhashstr) > + return -1; > + > + if (virAsprintf((char **)&askKey.prompt, > + _("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; > + } > + > + if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) { > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("failed to retrieve decision to accept " > + "host key")); > + tmp = (char*)askKey.prompt; > + VIR_FREE(tmp); > + ssh_string_free_char(keyhashstr); > + return -1; > + } > + > + tmp = (char*)askKey.prompt; > + 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 error value")); > + 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) One argument per line please. > +{ > + virNetLibsshSessionPtr sess = userdata; > + virConnectCredential retr_passphrase; > + virBuffer buff = VIR_BUFFER_INITIALIZER; > + 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; > + } > + > + virBufferAdd(&buff, prompt, virLengthForPromptString(prompt)); > + > + if (virBufferCheckError(&buff) < 0) { > + virBufferFreeAndReset(&buff); The buffer is already freed on an error. > + return -1; > + } > + > + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); > + retr_passphrase.type = virCredTypeForPrompt(sess->cred, echo); > + retr_passphrase.prompt = virBufferCurrentContent(&buff); This shouldn't really be used. Please get the content into a variable. > + > + if (retr_passphrase.type == -1) { > + virBufferFreeAndReset(&buff); This is weird. We usually do a cleanup label, so that it does not have to be repeated ... > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("no suitable callback for input of key passphrase")); > + return -1; > + } > + > + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { > + virBufferFreeAndReset(&buff); ... > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("failed to retrieve private key passphrase: " > + "callback has failed")); > + return -1; > + } > + > + virBufferFreeAndReset(&buff); ... > + > + p = virStrncpy(buf, retr_passphrase.result, > + retr_passphrase.resultlen, len); > + VIR_FREE(retr_passphrase.result); Since this contains the passprhase, it should be overwritten. Please use one of the VIR_DISPOSE... helpers. > + if (!p) { > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("authentication buffer too long for provided " > + "passphrase")); The passphrase is too long for the buffer. > + return -1; > + } > + > + return 0; > +} > + > +/* 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); > + > + /* try open the key with the password set, first */ > + ret = ssh_pki_import_privkey_file(priv->filename, priv->password, > + virNetLibsshAuthenticatePrivkeyCb, > + sess, &private_key); > + if (ret == SSH_EOF) { > + virReportError(VIR_ERR_AUTH_FAILED, > + _("error while reading private key '%s'"), > + priv->filename); Isn't this overwriting an error reported by the callback trying to open the file? > + err = SSH_AUTH_ERROR; > + goto error; > + } else if (ret == SSH_ERROR) { > + virReportError(VIR_ERR_AUTH_FAILED, > + _("error while opening private key '%s', wrong " > + "passphrase?"), > + priv->filename); Same here. > + err = SSH_AUTH_ERROR; > + goto error; > + } > + > + 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) { > + /* 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'"), > + tmp); > + 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; > + } > + > + 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_FREE(password); Please destroy the password with VIR_DISPOSE... > + > + 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_FREE(password); See above. > + 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 (virBufferCheckError(&buff) < 0) { > + virBufferFreeAndReset(&buff); > + return -1; > + } > + > + for (iprompt = 0; iprompt < nprompts; iprompt++) { > + virConnectCredential retr_passphrase; > + const char *promptStr; > + char echo; > + virBuffer prompt = VIR_BUFFER_INITIALIZER; > + > + /* get the prompt, stripping the trailing newlines and spaces */ > + promptStr = ssh_userauth_kbdint_getprompt(sess->session, iprompt, > + &echo); > + > + /* compose the instruction buffer with this prompt */ > + if (virBufferUse(&buff) > 0) { > + virBufferAddBuffer(&prompt, &buff); > + virBufferAddChar(&prompt, '\n'); > + } > + virBufferAdd(&prompt, promptStr, > + virLengthForPromptString(promptStr)); > + > + if (virBufferCheckError(&prompt) < 0) { > + virBufferFreeAndReset(&buff); > + virBufferFreeAndReset(&prompt); > + return -1; > + } > + > + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); > + retr_passphrase.type = virCredTypeForPrompt(sess->cred, echo); > + retr_passphrase.prompt = virBufferCurrentContent(&prompt); Please don't use virBufferCurrentContent. Get the pointer and be responsible for it's lifecycle. > + > + if (retr_passphrase.type == -1) { > + virBufferFreeAndReset(&buff); > + virBufferFreeAndReset(&prompt); A lot of shared cleanup code ... > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("no suitable callback for input of key " > + "passphrase")); > + return SSH_AUTH_ERROR; > + } > + > + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { > + virBufferFreeAndReset(&buff); > + virBufferFreeAndReset(&prompt); ... > + virReportError(VIR_ERR_LIBSSH, "%s", > + _("failed to retrieve keyboard interactive " > + "result: callback has failed")); > + return SSH_AUTH_ERROR; > + } > + > + ret = ssh_userauth_kbdint_setanswer(sess->session, iprompt, > + retr_passphrase.result); > + virBufferFreeAndReset(&prompt); > + VIR_FREE(retr_passphrase.result); VIR_DISPOSE... > + if (ret < 0) { > + virBufferFreeAndReset(&buff); > + errmsg = ssh_get_error(sess->session); > + virReportError(VIR_ERR_AUTH_FAILED, > + _("authentication failed: %s"), errmsg); > + return ret; > + } > + } > + > + 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 auth_failed = false; > + const char *errmsg; > + int ret; > + int methods; > + > + 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); > + > + if (methods & SSH_AUTH_METHOD_PUBLICKEY) { > + /* try to authenticate using ssh-agent first */ > + auth = sess->auths[VIR_NET_LIBSSH_AUTH_AGENT]; > + if (auth) { > + ret = ssh_userauth_publickey_auto(sess->session, NULL, NULL); > + if (ret == SSH_AUTH_ERROR) { > + errmsg = ssh_get_error(sess->session); > + virReportError(VIR_ERR_LIBSSH, > + _("failed to authenticate using agent: %s"), > + errmsg); > + return -1; > + } else if (ret == SSH_AUTH_SUCCESS) { > + /* authenticated */ > + return 0; > + } > + } > + > + /* try to authenticate using the provided ssh key, if any */ > + auth = sess->auths[VIR_NET_LIBSSH_AUTH_PRIVKEY]; > + if (auth) { > + ret = virNetLibsshAuthenticatePrivkey(sess, auth); > + if (ret == SSH_AUTH_ERROR) { > + /* virReportError is called already */ > + return -1; > + } else if (ret == SSH_AUTH_SUCCESS) { > + /* authenticated */ > + return 0; > + } > + } > + > + auth_failed = true; > + } > + > + if (methods & SSH_AUTH_METHOD_INTERACTIVE) { > + /* try to authenticate using the keyboard interactive way */ > + auth = sess->auths[VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE]; > + if (auth) { > + ret = virNetLibsshAuthenticateKeyboardInteractive(sess, auth); > + if (ret == SSH_AUTH_ERROR) { > + /* virReportError is called already */ > + return -1; > + } else if (ret == SSH_AUTH_SUCCESS) { > + /* authenticated */ > + return 0; > + } > + } > + > + auth_failed = true; > + } > + > + if (methods & SSH_AUTH_METHOD_PASSWORD) { > + /* try to authenticate with password */ > + auth = sess->auths[VIR_NET_LIBSSH_AUTH_PASSWORD]; > + if (auth) { > + ret = virNetLibsshAuthenticatePassword(sess, auth); > + if (ret == SSH_AUTH_ERROR) { > + /* virReportError is called already */ > + return -1; > + } else if (ret == SSH_AUTH_SUCCESS) { > + /* authenticated */ > + return 0; > + } > + } > + > + auth_failed = true; > + } > + > + if (!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 < ARRAY_CARDINALITY(sess->auths); ++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; > +} > + > +void > +virNetLibsshSessionAuthReset(virNetLibsshSessionPtr sess) > +{ > + virObjectLock(sess); > + virNetLibsshSessionAuthMethodsFree(sess); > + virObjectUnlock(sess); > +} > + > +int > +virNetLibsshSessionAuthAddPasswordAuth(virNetLibsshSessionPtr sess, > + virURIPtr uri) > +{ > + virNetLibsshAuthMethodPtr auth; > + > + if (uri) { > + VIR_FREE(sess->authPath); > + > + if (virAuthGetConfigFilePathURI(uri, &sess->authPath) < 0) > + goto error; > + } > + > + virObjectLock(sess); > + > + if (!(auth = virNetLibsshSessionAuthMethodNew(sess, > + VIR_NET_LIBSSH_AUTH_PASSWORD))) > + goto error; > + > + virObjectUnlock(sess); > + return 0; > + > + error: > + virObjectUnlock(sess); > + return -1; Both success and failure paths can be merged into one cleanup path by introducing a return variable. > +} > + > +int > +virNetLibsshSessionAuthAddAgentAuth(virNetLibsshSessionPtr sess) > +{ > + virNetLibsshAuthMethodPtr auth; > + > + virObjectLock(sess); > + > + if (!(auth = virNetLibsshSessionAuthMethodNew(sess, > + VIR_NET_LIBSSH_AUTH_AGENT))) > + goto error; > + > + virObjectUnlock(sess); > + return 0; > + > + error: > + virObjectUnlock(sess); > + return -1; Same here. > +} > + > +int > +virNetLibsshSessionAuthAddPrivKeyAuth(virNetLibsshSessionPtr sess, > + const char *keyfile, > + const char *password) > +{ > + 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")); > + return -1; > + } > + > + virObjectLock(sess); > + > + if (VIR_STRDUP(file, keyfile) < 0 || > + VIR_STRDUP(pass, password) < 0) > + goto error; > + > + if (!(auth = virNetLibsshSessionAuthMethodNew(sess, > + VIR_NET_LIBSSH_AUTH_PRIVKEY))) > + goto error; > + > + auth->password = pass; > + auth->filename = file; > + > + virObjectUnlock(sess); > + return 0; > + > + error: > + VIR_FREE(pass); > + VIR_FREE(file); > + virObjectUnlock(sess); > + return -1; > +} > + > +int > +virNetLibsshSessionAuthAddKeyboardAuth(virNetLibsshSessionPtr sess, > + int tries) > +{ > + virNetLibsshAuthMethodPtr auth; > + > + virObjectLock(sess); > + > + if (!(auth = virNetLibsshSessionAuthMethodNew(sess, > + VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE))) > + goto error; > + > + auth->tries = tries; > + > + virObjectUnlock(sess); > + return 0; > + > + error: > + virObjectUnlock(sess); > + return -1; Same as above. > + > +} > + > +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) Is this guaranteed to copy the socket number at call time? Otherwise (similarly to the ones above will not work reliably). > + 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(sess->channel, > + buf + read_n, > + len - read_n, > + 0); > + > + if (ret == SSH_AGAIN || (ret == 0 && !ssh_channel_is_eof(sess->channel))) > + 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(sess->channel, > + sess->rbuf, > + VIR_NET_LIBSSH_BUFFER_SIZE, > + 0); > + > + if (ret == SSH_AGAIN || (ret == 0 && !ssh_channel_is_eof(sess->channel))) > + 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(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)) { > + 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; > +} Please make sure you run "make check" and "make syntax-check". I've got the following failures: GEN spacing-check Curly brackets around single-line body: src/rpc/virnetlibsshsession.c:250-252: if (type == VIR_CRED_ECHOPROMPT) { return type; } maint.mk: incorrect formatting, see HACKING for rules --- ./po/POTFILES.in +++ ./po/POTFILES.in @@ -144,6 +144,7 @@ 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 maint.mk: you have changed the set of files with translatable diagnostics; apply the above patch The rest looks reasonable. Peter
Attachment:
signature.asc
Description: Digital signature
-- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list