[libvirt] [PATCH v4 5/7] remote: expose a new libssh transport

Pino Toscano ptoscano at redhat.com
Wed Nov 2 14:18:09 UTC 2016


Implement in virtNetClient and VirNetSocket the needed functions to
expose a new libssh transport, providing all the options that the
libssh2 transport supports.
---
 docs/remote.html.in        |  35 ++++++---
 src/remote/remote_driver.c |  41 +++++++++++
 src/rpc/virnetclient.c     | 106 +++++++++++++++++++++++++++
 src/rpc/virnetclient.h     |  13 ++++
 src/rpc/virnetsocket.c     | 179 +++++++++++++++++++++++++++++++++++++++++++++
 src/rpc/virnetsocket.h     |  13 ++++
 6 files changed, 375 insertions(+), 12 deletions(-)

diff --git a/docs/remote.html.in b/docs/remote.html.in
index 4c3012f..c28a505 100644
--- a/docs/remote.html.in
+++ b/docs/remote.html.in
@@ -145,6 +145,13 @@ of the OpenSSH binary. This transport uses the libvirt authentication callback f
 all ssh authentication calls and therefore supports keyboard-interactive authentication
 even with graphical management applications. As with the classic ssh transport
 netcat is required on the remote side.</dd>
+      <dt><code>libssh</code></dt>
+      <dd> Transport over the SSH protocol using
+      <a href="http://libssh.org/" title="libssh homepage">libssh</a> instead
+of the OpenSSH binary. This transport uses the libvirt authentication callback for
+all ssh authentication calls and therefore supports keyboard-interactive authentication
+even with graphical management applications. As with the classic ssh transport
+netcat is required on the remote side.</dd>
     </dl>
     <p>
 The default transport, if no other is specified, is <code>tls</code>.
@@ -192,6 +199,9 @@ settings.
 <li><code>qemu+libssh2://user at host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/> &#x2014;
 Connect to a remote host using a ssh connection with the libssh2 driver
 and use a different known_hosts file.</li>
+<li><code>qemu+libssh://user@host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/> &#x2014;
+Connect to a remote host using a ssh connection with the libssh driver
+and use a different known_hosts file.</li>
     </ul>
     <h4>
       <a name="Remote_URI_parameters">Extra parameters</a>
@@ -260,7 +270,7 @@ Note that parameter values must be
         <td>
           <code>socket</code>
         </td>
-        <td> unix, ssh, libssh2 </td>
+        <td> unix, ssh, libssh2, libssh </td>
         <td>
   The path to the Unix domain socket, which overrides the
   compiled-in default.  For ssh transport, this is passed to
@@ -275,7 +285,7 @@ Note that parameter values must be
         <td>
           <code>netcat</code>
         </td>
-        <td> ssh, libssh2 </td>
+        <td> ssh, libssh2, libssh </td>
         <td>
   The name of the netcat command on the remote machine.
   The default is <code>nc</code>.  For ssh transport, libvirt
@@ -300,7 +310,7 @@ Note that parameter values must be
         <td>
           <code>keyfile</code>
         </td>
-        <td> ssh, libssh2 </td>
+        <td> ssh, libssh2, libssh </td>
         <td>
   The name of the private key file to use to authentication to the remote
   machine.  If this option is not used the default keys are used.
@@ -368,14 +378,15 @@ Note that parameter values must be
         <td>
           <code>known_hosts</code>
         </td>
-        <td> libssh2 </td>
-        <td>
-  Path to the known_hosts file to verify the host key against. LibSSH2
-  supports OpenSSH-style known_hosts files, although it does not support
-  all key types, so using files created by the OpenSSH binary may result
-  into truncating the known_hosts file. It's recommended to use the default
-  known_hosts file is located in libvirt's client local configuration
-  directory e.g.: ~/.config/libvirt/known_hosts. Note: Use absolute paths.
+        <td> libssh2, libssh </td>
+        <td>
+  Path to the known_hosts file to verify the host key against. LibSSH2 and
+  libssh support OpenSSH-style known_hosts files, although LibSSH2 does not
+  support all key types, so using files created by the OpenSSH binary may
+  result into truncating the known_hosts file. Thus, with LibSSH2 it's
+  recommended to use the default known_hosts file is located in libvirt's
+  client local configuration directory e.g.: ~/.config/libvirt/known_hosts.
+  Note: Use absolute paths.
 </td>
       </tr>
       <tr>
@@ -386,7 +397,7 @@ Note that parameter values must be
         <td>
           <code>sshauth</code>
         </td>
-        <td> libssh2 </td>
+        <td> libssh2, libssh </td>
         <td>
   A comma separated list of authentication methods to use. Default (is
   "agent,privkey,keyboard-interactive". The order of the methods is preserved.
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) {
diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c
index 713b8d5..34475d9 100644
--- a/src/rpc/virnetclient.c
+++ b/src/rpc/virnetclient.c
@@ -536,6 +536,112 @@ 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 */
+    if (confdir) {
+        if (!knownHostsPath) {
+            if (virFileExists(confdir)) {
+                if (virAsprintf(&knownhosts, "%s/known_hosts", confdir) < 0)
+                    goto cleanup;
+            }
+        } else {
+            if (VIR_STRDUP(knownhosts, knownHostsPath) < 0)
+                goto cleanup;
+        }
+    }
+
+    if (homedir) {
+        if (!privkeyPath) {
+            if (virNetClientFindDefaultSshKey(homedir, &privkey) < 0)
+                goto no_memory;
+        } 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;
+
+    if (virAsprintf(&command,
+         "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) < 0)
+        goto cleanup;
+
+    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);
diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c
index 05f20a5..325a7c7 100644
--- a/src/rpc/virnetsocket.c
+++ b/src/rpc/virnetsocket.c
@@ -65,6 +65,10 @@
 # include "virnetsshsession.h"
 #endif
 
+#if WITH_LIBSSH
+# include "virnetlibsshsession.h"
+#endif
+
 #define VIR_FROM_THIS VIR_FROM_RPC
 
 VIR_LOG_INIT("rpc.netsocket");
@@ -107,6 +111,9 @@ struct _virNetSocket {
 #if WITH_SSH2
     virNetSSHSessionPtr sshSession;
 #endif
+#if WITH_LIBSSH
+    virNetLibsshSessionPtr libsshSession;
+#endif
 };
 
 
@@ -1027,6 +1034,143 @@ virNetSocketNewConnectLibSSH2(const char *host ATTRIBUTE_UNUSED,
 }
 #endif /* WITH_SSH2 */
 
+#if WITH_LIBSSH
+int
+virNetSocketNewConnectLibssh(const char *host,
+                             const char *port,
+                             int family,
+                             const char *username,
+                             const char *privkey,
+                             const char *knownHosts,
+                             const char *knownHostsVerify,
+                             const char *authMethods,
+                             const char *command,
+                             virConnectAuthPtr auth,
+                             virURIPtr uri,
+                             virNetSocketPtr *retsock)
+{
+    virNetSocketPtr sock = NULL;
+    virNetLibsshSessionPtr sess = NULL;
+    unsigned int verify;
+    int ret = -1;
+    int portN;
+
+    char *authMethodNext = NULL;
+    char *authMethodsCopy = NULL;
+    char *authMethod;
+
+    /* port number will be verified while opening the socket */
+    if (virStrToLong_i(port, NULL, 10, &portN) < 0) {
+        virReportError(VIR_ERR_LIBSSH, "%s",
+                       _("Failed to parse port number"));
+        goto error;
+    }
+
+    /* create ssh session context */
+    if (!(sess = virNetLibsshSessionNew(username)))
+        goto error;
+
+    /* set ssh session parameters */
+    if (virNetLibsshSessionAuthSetCallback(sess, auth) != 0)
+        goto error;
+
+    if (STRCASEEQ("auto", knownHostsVerify)) {
+        verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_AUTO_ADD;
+    } else if (STRCASEEQ("ignore", knownHostsVerify)) {
+        verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE;
+    } else if (STRCASEEQ("normal", knownHostsVerify)) {
+        verify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL;
+    } else {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Invalid host key verification method: '%s'"),
+                       knownHostsVerify);
+        goto error;
+    }
+
+    if (virNetLibsshSessionSetHostKeyVerification(sess,
+                                                  host,
+                                                  portN,
+                                                  knownHosts,
+                                                  verify) != 0)
+        goto error;
+
+    if (virNetLibsshSessionSetChannelCommand(sess, command) != 0)
+        goto error;
+
+    if (VIR_STRDUP(authMethodsCopy, authMethods) < 0)
+        goto error;
+
+    authMethodNext = authMethodsCopy;
+
+    while ((authMethod = strsep(&authMethodNext, ","))) {
+        if (STRCASEEQ(authMethod, "keyboard-interactive")) {
+            ret = virNetLibsshSessionAuthAddKeyboardAuth(sess, -1);
+        } else if (STRCASEEQ(authMethod, "password")) {
+            ret = virNetLibsshSessionAuthAddPasswordAuth(sess, uri);
+        } else if (STRCASEEQ(authMethod, "privkey")) {
+            ret = virNetLibsshSessionAuthAddPrivKeyAuth(sess,
+                                                        privkey,
+                                                        NULL);
+        } else if (STRCASEEQ(authMethod, "agent")) {
+            ret = virNetLibsshSessionAuthAddAgentAuth(sess);
+        } else {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("Invalid authentication method: '%s'"),
+                           authMethod);
+            ret = -1;
+            goto error;
+        }
+
+        if (ret != 0)
+            goto error;
+    }
+
+    /* connect to remote server */
+    if ((ret = virNetSocketNewConnectTCP(host, port, family, &sock)) < 0)
+        goto error;
+
+    /* connect to the host using ssh */
+    if ((ret = virNetLibsshSessionConnect(sess, virNetSocketGetFD(sock))) != 0)
+        goto error;
+
+    sock->libsshSession = sess;
+    /* libssh owns the FD and closes it on its own, and thus
+     * we must not close it (otherwise there are warnings about
+     * trying to close an invalid FD).
+     */
+    sock->ownsFd = false;
+    *retsock = sock;
+
+    VIR_FREE(authMethodsCopy);
+    return 0;
+
+ error:
+    virObjectUnref(sock);
+    virObjectUnref(sess);
+    VIR_FREE(authMethodsCopy);
+    return ret;
+}
+#else
+int
+virNetSocketNewConnectLibssh(const char *host ATTRIBUTE_UNUSED,
+                             const char *port ATTRIBUTE_UNUSED,
+                             int family ATTRIBUTE_UNUSED,
+                             const char *username ATTRIBUTE_UNUSED,
+                             const char *privkey ATTRIBUTE_UNUSED,
+                             const char *knownHosts ATTRIBUTE_UNUSED,
+                             const char *knownHostsVerify ATTRIBUTE_UNUSED,
+                             const char *authMethods ATTRIBUTE_UNUSED,
+                             const char *command ATTRIBUTE_UNUSED,
+                             virConnectAuthPtr auth ATTRIBUTE_UNUSED,
+                             virURIPtr uri ATTRIBUTE_UNUSED,
+                             virNetSocketPtr *retsock ATTRIBUTE_UNUSED)
+{
+    virReportSystemError(ENOSYS, "%s",
+                         _("libssh transport support was not enabled"));
+    return -1;
+}
+#endif /* WITH_LIBSSH */
+
 int virNetSocketNewConnectExternal(const char **cmdargv,
                                    virNetSocketPtr *retsock)
 {
@@ -1204,6 +1348,10 @@ void virNetSocketDispose(void *obj)
     virObjectUnref(sock->sshSession);
 #endif
 
+#if WITH_LIBSSH
+    virObjectUnref(sock->libsshSession);
+#endif
+
     if (sock->ownsFd)
         VIR_FORCE_CLOSE(sock->fd);
     VIR_FORCE_CLOSE(sock->errfd);
@@ -1534,6 +1682,11 @@ bool virNetSocketHasCachedData(virNetSocketPtr sock ATTRIBUTE_UNUSED)
         hasCached = true;
 #endif
 
+#if WITH_LIBSSH
+    if (virNetLibsshSessionHasCachedData(sock->libsshSession))
+        hasCached = true;
+#endif
+
 #if WITH_SASL
     if (sock->saslDecoded)
         hasCached = true;
@@ -1558,6 +1711,22 @@ static ssize_t virNetSocketLibSSH2Write(virNetSocketPtr sock,
 }
 #endif
 
+#if WITH_LIBSSH
+static ssize_t virNetSocketLibsshRead(virNetSocketPtr sock,
+                                      char *buf,
+                                      size_t len)
+{
+    return virNetLibsshChannelRead(sock->libsshSession, buf, len);
+}
+
+static ssize_t virNetSocketLibsshWrite(virNetSocketPtr sock,
+                                       const char *buf,
+                                       size_t len)
+{
+    return virNetLibsshChannelWrite(sock->libsshSession, buf, len);
+}
+#endif
+
 bool virNetSocketHasPendingData(virNetSocketPtr sock ATTRIBUTE_UNUSED)
 {
     bool hasPending = false;
@@ -1581,6 +1750,11 @@ static ssize_t virNetSocketReadWire(virNetSocketPtr sock, char *buf, size_t len)
         return virNetSocketLibSSH2Read(sock, buf, len);
 #endif
 
+#if WITH_LIBSSH
+    if (sock->libsshSession)
+        return virNetSocketLibsshRead(sock, buf, len);
+#endif
+
  reread:
 #if WITH_GNUTLS
     if (sock->tlsSession &&
@@ -1640,6 +1814,11 @@ static ssize_t virNetSocketWriteWire(virNetSocketPtr sock, const char *buf, size
         return virNetSocketLibSSH2Write(sock, buf, len);
 #endif
 
+#if WITH_LIBSSH
+    if (sock->libsshSession)
+        return virNetSocketLibsshWrite(sock, buf, len);
+#endif
+
  rewrite:
 #if WITH_GNUTLS
     if (sock->tlsSession &&
diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h
index ec064bb..56c75c0 100644
--- a/src/rpc/virnetsocket.h
+++ b/src/rpc/virnetsocket.h
@@ -100,6 +100,19 @@ int virNetSocketNewConnectLibSSH2(const char *host,
                                   virURIPtr uri,
                                   virNetSocketPtr *retsock);
 
+int virNetSocketNewConnectLibssh(const char *host,
+                                 const char *port,
+                                 int family,
+                                 const char *username,
+                                 const char *privkey,
+                                 const char *knownHosts,
+                                 const char *knownHostsVerify,
+                                 const char *authMethods,
+                                 const char *command,
+                                 virConnectAuthPtr auth,
+                                 virURIPtr uri,
+                                 virNetSocketPtr *retsock);
+
 int virNetSocketNewConnectExternal(const char **cmdargv,
                                    virNetSocketPtr *addr);
 
-- 
2.7.4




More information about the libvir-list mailing list