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 <pkre...@redhat.com>
> + * Author: Pino Toscano <ptosc...@redhat.com>
> + */
> +#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@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list

Reply via email to