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 | 118 ++++++++++++++++++++++++++++++
src/rpc/virnetclient.h | 13 ++++
src/rpc/virnetsocket.c | 179 +++++++++++++++++++++++++++++++++++++++++++++
src/rpc/virnetsocket.h | 13 ++++
6 files changed, 387 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@host/system?known_hosts=/home/user/.ssh/known_hosts</code><br/>
—
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/>
—
+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 361dc1a..6d406ff 100644
--- a/src/rpc/virnetclient.c
+++ b/src/rpc/virnetclient.c
@@ -505,6 +505,124 @@ 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) {
+ /* RSA */
+ if (virAsprintf(&privkey, "%s/.ssh/id_rsa", homedir) < 0)
+ goto cleanup;
+
+ if (!(virFileExists(privkey)))
+ VIR_FREE(privkey);
+ /* DSA */
+ if (!privkey) {
+ if (virAsprintf(&privkey, "%s/.ssh/id_dsa", homedir) <
0)
+ goto cleanup;
+
+ 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;
+
+ 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