[libvirt] [PATCH 0/5] Add LibSSH2 transport

This patchset adds LibSSH2 transport implementation for libvirt's virNetSocket object and other needed places to enable connections using the native library instead of spawning the ssh client and communicating using pipes. This patchset lacks addition of documentation (I'll do that later, but I'd like to get the code sorted out) but otherwise should be fully functional. The commit message to the last patch describes the configuration variables used to set parameters of the transport. The setting is done using uri arguments. Thanks in advance for reviews and comments :) Peter Peter Krempa (5): remote: Clean up coding style and refactor remote connection opening libssh2_transport: add main libssh2 transport implementation libssh2_transport: add ssh context support to virNetSocket libssh2_transport: Add libssh2 session support to net client code libssh2_transport: Use libssh2 driver code in remote driver configure.ac | 40 +- include/libvirt/virterror.h | 2 + po/POTFILES.in | 1 + src/Makefile.am | 9 + src/libvirt_private.syms | 1 + src/remote/remote_driver.c | 339 ++++++----- src/rpc/virnetclient.c | 120 ++++- src/rpc/virnetclient.h | 14 +- src/rpc/virnetlibsshcontext.c | 1444 +++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetlibsshcontext.h | 83 +++ src/rpc/virnetsocket.c | 178 +++++- src/rpc/virnetsocket.h | 13 + src/util/virterror.c | 8 +- 13 files changed, 2092 insertions(+), 160 deletions(-) create mode 100644 src/rpc/virnetlibsshcontext.c create mode 100644 src/rpc/virnetlibsshcontext.h -- 1.7.8.6

Remove spaces before fucntion calls and some other coding nits in some parts of the remote driver and refactor getting of URI argument components into variables used by libvirt later on. --- src/remote/remote_driver.c | 292 +++++++++++++++++++++----------------------- 1 files changed, 142 insertions(+), 150 deletions(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index afd367b..f643bbe 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -346,6 +346,29 @@ static void remoteClientCloseFunc(virNetClientPtr client ATTRIBUTE_UNUSED, virMutexUnlock(&conn->lock); } +/* helper macro to ease extraction of arguments from the URI */ +#define EXTRACT_URI_ARG_STR(ARG_NAME, ARG_VAR) \ + if (STRCASEEQ(var->name, ARG_NAME)) { \ + VIR_FREE(ARG_VAR); \ + if (!(ARG_VAR = strdup(var->value))) \ + goto no_memory; \ + var->ignore = 1; \ + continue; \ + } + +#define EXTRACT_URI_ARG_BOOL(ARG_NAME, ARG_VAR) \ + if (STRCASEEQ(var->name, ARG_NAME)) { \ + int tmp; \ + if (virStrToLong_i(var->value, NULL, 10, &tmp) < 0) { \ + virReportError(VIR_ERR_INVALID_ARG, \ + _("Failed to parse value of URI component %s"), \ + var->name); \ + goto failed; \ + } \ + ARG_VAR = tmp == 0; \ + var->ignore = 1; \ + continue; \ + } /* * URIs that this driver needs to handle: * @@ -364,10 +387,10 @@ static void remoteClientCloseFunc(virNetClientPtr client ATTRIBUTE_UNUSED, * - xxx:/// -> UNIX domain socket */ static int -doRemoteOpen (virConnectPtr conn, - struct private_data *priv, - virConnectAuthPtr auth ATTRIBUTE_UNUSED, - unsigned int flags) +doRemoteOpen(virConnectPtr conn, + struct private_data *priv, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + unsigned int flags) { char *transport_str = NULL; enum { @@ -381,7 +404,7 @@ doRemoteOpen (virConnectPtr conn, const char *daemonPath; #endif - /* We handle *ALL* URIs here. The caller has rejected any + /* We handle *ALL* URIs here. The caller has rejected any * URIs we don't care about */ if (conn->uri) { @@ -394,7 +417,7 @@ doRemoteOpen (virConnectPtr conn, transport = trans_unix; } else { - transport_str = get_transport_from_scheme (conn->uri->scheme); + transport_str = get_transport_from_scheme(conn->uri->scheme); if (!transport_str) { if (conn->uri->server) @@ -402,9 +425,9 @@ doRemoteOpen (virConnectPtr conn, else transport = trans_unix; } else { - if (STRCASEEQ (transport_str, "tls")) + if (STRCASEEQ(transport_str, "tls")) transport = trans_tls; - else if (STRCASEEQ (transport_str, "unix")) { + else if (STRCASEEQ(transport_str, "unix")) { if (conn->uri->server) { virReportError(VIR_ERR_INVALID_ARG, _("using unix socket and remote " @@ -414,11 +437,11 @@ doRemoteOpen (virConnectPtr conn, } else { transport = trans_unix; } - } else if (STRCASEEQ (transport_str, "ssh")) + } else if (STRCASEEQ(transport_str, "ssh")) transport = trans_ssh; - else if (STRCASEEQ (transport_str, "ext")) + else if (STRCASEEQ(transport_str, "ext")) transport = trans_ext; - else if (STRCASEEQ (transport_str, "tcp")) + else if (STRCASEEQ(transport_str, "tcp")) transport = trans_tcp; else { virReportError(VIR_ERR_INVALID_ARG, "%s", @@ -446,26 +469,27 @@ doRemoteOpen (virConnectPtr conn, /* Remote server defaults to "localhost" if not specified. */ if (conn->uri && conn->uri->port != 0) { - if (virAsprintf(&port, "%d", conn->uri->port) == -1) goto out_of_memory; + if (virAsprintf(&port, "%d", conn->uri->port) < 0) + goto no_memory; } else if (transport == trans_tls) { - port = strdup (LIBVIRTD_TLS_PORT); - if (!port) goto out_of_memory; + if (!(port = strdup(LIBVIRTD_TLS_PORT))) + goto no_memory; } else if (transport == trans_tcp) { - port = strdup (LIBVIRTD_TCP_PORT); - if (!port) goto out_of_memory; - } else - port = NULL; /* Port not used for unix, ext., default for ssh */ + if (!(port = strdup(LIBVIRTD_TCP_PORT))) + goto no_memory; + } /* Port not used for unix, ext., default for ssh */ + if (conn->uri && conn->uri->server) + priv->hostname = strdup(conn->uri->server); + else + priv->hostname = strdup("localhost"); - priv->hostname = strdup (conn->uri && conn->uri->server ? - conn->uri->server : "localhost"); if (!priv->hostname) - goto out_of_memory; - if (conn->uri && conn->uri->user) { - username = strdup (conn->uri->user); - if (!username) - goto out_of_memory; - } + goto no_memory; + + if ((conn->uri && conn->uri->user) && + !(username = strdup (conn->uri->user))) + goto no_memory; /* Get the variables from the query string. * Then we need to reconstruct the query string (because @@ -477,56 +501,26 @@ doRemoteOpen (virConnectPtr conn, if (conn->uri) { for (i = 0; i < conn->uri->paramsCount ; i++) { virURIParamPtr var = &conn->uri->params[i]; - if (STRCASEEQ (var->name, "name")) { - VIR_FREE(name); - name = strdup (var->value); - if (!name) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "command")) { - VIR_FREE(command); - command = strdup (var->value); - if (!command) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "socket")) { - VIR_FREE(sockname); - sockname = strdup (var->value); - if (!sockname) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "auth")) { - VIR_FREE(authtype); - authtype = strdup (var->value); - if (!authtype) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "netcat")) { - VIR_FREE(netcat); - netcat = strdup (var->value); - if (!netcat) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "keyfile")) { - VIR_FREE(keyfile); - keyfile = strdup (var->value); - if (!keyfile) goto out_of_memory; - } else if (STRCASEEQ (var->name, "no_sanity")) { - sanity = atoi(var->value) == 0; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "no_verify")) { - verify = atoi (var->value) == 0; - var->ignore = 1; - } else if (STRCASEEQ (var->name, "no_tty")) { - tty = atoi (var->value) == 0; - var->ignore = 1; - } else if (STRCASEEQ(var->name, "pkipath")) { - VIR_FREE(pkipath); - pkipath = strdup(var->value); - if (!pkipath) goto out_of_memory; - var->ignore = 1; - } else if (STRCASEEQ(var->name, "authfile")) { + EXTRACT_URI_ARG_STR("name", name); + EXTRACT_URI_ARG_STR("command", command); + EXTRACT_URI_ARG_STR("socket", sockname); + EXTRACT_URI_ARG_STR("auth", authtype); + EXTRACT_URI_ARG_STR("netcat", netcat); + EXTRACT_URI_ARG_STR("keyfile", keyfile); + EXTRACT_URI_ARG_STR("pkipath", pkipath); + + EXTRACT_URI_ARG_BOOL("no_sanity", sanity); + EXTRACT_URI_ARG_BOOL("no_verify", verify); + EXTRACT_URI_ARG_BOOL("no_tty", tty); + + if (STRCASEEQ(var->name, "authfile")) { /* Strip this param, used by virauth.c */ var->ignore = 1; - } else { - VIR_DEBUG("passing through variable '%s' ('%s') to remote end", - var->name, var->value); + continue; } + + VIR_DEBUG("passing through variable '%s' ('%s') to remote end", + var->name, var->value); } /* Construct the original name. */ @@ -536,7 +530,7 @@ doRemoteOpen (virConnectPtr conn, STRPREFIX(conn->uri->scheme, "remote+"))) { /* Allow remote serve to probe */ if (!(name = strdup(""))) - goto out_of_memory; + goto no_memory; } else { virURI tmpuri = { .scheme = conn->uri->scheme, @@ -547,7 +541,7 @@ doRemoteOpen (virConnectPtr conn, /* Evil, blank out transport scheme temporarily */ if (transport_str) { - assert (transport_str[-1] == '+'); + assert(transport_str[-1] == '+'); transport_str[-1] = '\0'; } @@ -566,7 +560,7 @@ doRemoteOpen (virConnectPtr conn, } else { /* Probe URI server side */ if (!(name = strdup(""))) - goto out_of_memory; + goto no_memory; } VIR_DEBUG("proceeding with name = %s", name); @@ -578,7 +572,6 @@ doRemoteOpen (virConnectPtr conn, goto failed; } - VIR_DEBUG("Connecting with transport %d", transport); /* Connect to the remote service. */ switch (transport) { @@ -615,7 +608,7 @@ doRemoteOpen (virConnectPtr conn, if (virAsprintf(&sockname, "%s/" LIBVIRTD_USER_UNIX_SOCKET, userdir) < 0) { VIR_FREE(userdir); - goto out_of_memory; + goto no_memory; } VIR_FREE(userdir); } else { @@ -624,7 +617,7 @@ doRemoteOpen (virConnectPtr conn, else sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET); if (sockname == NULL) - goto out_of_memory; + goto no_memory; } VIR_DEBUG("Proceeding with sockname %s", sockname); } @@ -643,17 +636,16 @@ doRemoteOpen (virConnectPtr conn, break; case trans_ssh: - command = command ? command : strdup ("ssh"); - if (command == NULL) - goto out_of_memory; + if (!command && !(command = strdup("ssh"))) + goto no_memory; if (!sockname) { if (flags & VIR_DRV_OPEN_REMOTE_RO) sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET_RO); else sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET); - if (sockname == NULL) - goto out_of_memory; + if (!sockname) + goto no_memory; } if (!(priv->client = virNetClientNewSSH(priv->hostname, @@ -743,9 +735,9 @@ doRemoteOpen (virConnectPtr conn, remote_open_args args = { &name, flags }; VIR_DEBUG("Trying to open URI %s", name); - if (call (conn, priv, 0, REMOTE_PROC_OPEN, - (xdrproc_t) xdr_remote_open_args, (char *) &args, - (xdrproc_t) xdr_void, (char *) NULL) == -1) + if (call(conn, priv, 0, REMOTE_PROC_OPEN, + (xdrproc_t) xdr_remote_open_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) goto failed; } @@ -754,11 +746,11 @@ doRemoteOpen (virConnectPtr conn, remote_get_uri_ret uriret; VIR_DEBUG("Trying to query remote URI"); - memset (&uriret, 0, sizeof(uriret)); - if (call (conn, priv, 0, - REMOTE_PROC_GET_URI, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_remote_get_uri_ret, (char *) &uriret) < 0) + memset(&uriret, 0, sizeof(uriret)); + if (call(conn, priv, 0, + REMOTE_PROC_GET_URI, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_remote_get_uri_ret, (char *) &uriret) < 0) goto failed; VIR_DEBUG("Auto-probed URI is %s", uriret.uri); @@ -788,10 +780,9 @@ doRemoteOpen (virConnectPtr conn, return retcode; - out_of_memory: +no_memory: virReportOOMError(); - - failed: +failed: virNetClientProgramFree(priv->remoteProgram); virNetClientProgramFree(priv->qemuProgram); virNetClientClose(priv->client); @@ -801,6 +792,8 @@ doRemoteOpen (virConnectPtr conn, VIR_FREE(priv->hostname); goto cleanup; } +#undef EXTRACT_URI_ARG_STR +#undef EXTRACT_URI_ARG_BOOL static struct private_data * remoteAllocPrivateData(void) @@ -924,23 +917,23 @@ remoteOpen (virConnectPtr conn, /* In a string "driver+transport" return a pointer to "transport". */ static char * -get_transport_from_scheme (char *scheme) +get_transport_from_scheme(char *scheme) { - char *p = strchr (scheme, '+'); - return p ? p+1 : 0; + char *p = strchr(scheme, '+'); + return p ? p + 1 : NULL; } /*----------------------------------------------------------------------*/ static int -doRemoteClose (virConnectPtr conn, struct private_data *priv) +doRemoteClose(virConnectPtr conn, struct private_data *priv) { int ret = 0; - if (call (conn, priv, 0, REMOTE_PROC_CLOSE, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_void, (char *) NULL) == -1) + if (call(conn, priv, 0, REMOTE_PROC_CLOSE, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_void, (char *) NULL) == -1) ret = -1; virNetTLSContextFree(priv->tls); @@ -965,7 +958,7 @@ doRemoteClose (virConnectPtr conn, struct private_data *priv) } static int -remoteClose (virConnectPtr conn) +remoteClose(virConnectPtr conn) { int ret = 0; struct private_data *priv = conn->privateData; @@ -995,7 +988,7 @@ remoteClose (virConnectPtr conn) * http://www.redhat.com/archives/libvir-list/2007-February/msg00096.html */ static const char * -remoteType (virConnectPtr conn) +remoteType(virConnectPtr conn) { char *rv = NULL; remote_get_type_ret ret; @@ -1009,10 +1002,10 @@ remoteType (virConnectPtr conn) goto done; } - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_GET_TYPE, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_remote_get_type_ret, (char *) &ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_GET_TYPE, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_remote_get_type_ret, (char *) &ret) == -1) goto done; /* Stash. */ @@ -1030,10 +1023,10 @@ static int remoteIsSecure(virConnectPtr conn) remote_is_secure_ret ret; remoteDriverLock(priv); - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_IS_SECURE, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_remote_is_secure_ret, (char *) &ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_IS_SECURE, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_remote_is_secure_ret, (char *) &ret) == -1) goto done; /* We claim to be secure, if the remote driver @@ -1059,10 +1052,10 @@ static int remoteIsEncrypted(virConnectPtr conn) remote_is_secure_ret ret; remoteDriverLock(priv); - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_IS_SECURE, - (xdrproc_t) xdr_void, (char *) NULL, - (xdrproc_t) xdr_remote_is_secure_ret, (char *) &ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_IS_SECURE, + (xdrproc_t) xdr_void, (char *) NULL, + (xdrproc_t) xdr_remote_is_secure_ret, (char *) &ret) == -1) goto done; if (virNetClientIsEncrypted(priv->client)) @@ -1084,10 +1077,10 @@ done: } static int -remoteNodeGetCPUStats (virConnectPtr conn, - int cpuNum, - virNodeCPUStatsPtr params, int *nparams, - unsigned int flags) +remoteNodeGetCPUStats(virConnectPtr conn, + int cpuNum, + virNodeCPUStatsPtr params, int *nparams, + unsigned int flags) { int rv = -1; remote_node_get_cpu_stats_args args; @@ -1101,12 +1094,12 @@ remoteNodeGetCPUStats (virConnectPtr conn, args.cpuNum = cpuNum; args.flags = flags; - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_NODE_GET_CPU_STATS, - (xdrproc_t) xdr_remote_node_get_cpu_stats_args, - (char *) &args, - (xdrproc_t) xdr_remote_node_get_cpu_stats_ret, - (char *) &ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_NODE_GET_CPU_STATS, + (xdrproc_t) xdr_remote_node_get_cpu_stats_args, + (char *) &args, + (xdrproc_t) xdr_remote_node_get_cpu_stats_ret, + (char *) &ret) == -1) goto done; /* Check the length of the returned list carefully. */ @@ -1142,18 +1135,18 @@ remoteNodeGetCPUStats (virConnectPtr conn, rv = 0; cleanup: - xdr_free ((xdrproc_t) xdr_remote_node_get_cpu_stats_ret, - (char *) &ret); + xdr_free((xdrproc_t) xdr_remote_node_get_cpu_stats_ret, (char *) &ret); done: remoteDriverUnlock(priv); return rv; } static int -remoteNodeGetMemoryStats (virConnectPtr conn, - int cellNum, - virNodeMemoryStatsPtr params, int *nparams, - unsigned int flags) +remoteNodeGetMemoryStats(virConnectPtr conn, + int cellNum, + virNodeMemoryStatsPtr params, + int *nparams, + unsigned int flags) { int rv = -1; remote_node_get_memory_stats_args args; @@ -1168,9 +1161,9 @@ remoteNodeGetMemoryStats (virConnectPtr conn, args.flags = flags; memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_NODE_GET_MEMORY_STATS, - (xdrproc_t) xdr_remote_node_get_memory_stats_args, (char *) &args, - (xdrproc_t) xdr_remote_node_get_memory_stats_ret, (char *) &ret) == -1) + if (call(conn, priv, 0, REMOTE_PROC_NODE_GET_MEMORY_STATS, + (xdrproc_t) xdr_remote_node_get_memory_stats_args, (char *) &args, + (xdrproc_t) xdr_remote_node_get_memory_stats_ret, (char *) &ret) == -1) goto done; /* Check the length of the returned list carefully. */ @@ -1206,8 +1199,7 @@ remoteNodeGetMemoryStats (virConnectPtr conn, rv = 0; cleanup: - xdr_free ((xdrproc_t) xdr_remote_node_get_memory_stats_ret, - (char *) &ret); + xdr_free((xdrproc_t) xdr_remote_node_get_memory_stats_ret, (char *) &ret); done: remoteDriverUnlock(priv); return rv; @@ -1215,9 +1207,9 @@ done: static int remoteNodeGetCellsFreeMemory(virConnectPtr conn, - unsigned long long *freeMems, - int startCell, - int maxCells) + unsigned long long *freeMems, + int startCell, + int maxCells) { int rv = -1; remote_node_get_cells_free_memory_args args; @@ -1237,10 +1229,10 @@ remoteNodeGetCellsFreeMemory(virConnectPtr conn, args.startCell = startCell; args.maxcells = maxCells; - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY, - (xdrproc_t) xdr_remote_node_get_cells_free_memory_args, (char *)&args, - (xdrproc_t) xdr_remote_node_get_cells_free_memory_ret, (char *)&ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_NODE_GET_CELLS_FREE_MEMORY, + (xdrproc_t) xdr_remote_node_get_cells_free_memory_args, (char *)&args, + (xdrproc_t) xdr_remote_node_get_cells_free_memory_ret, (char *)&ret) == -1) goto done; for (i = 0 ; i < ret.cells.cells_len ; i++) @@ -1256,7 +1248,7 @@ done: } static int -remoteListDomains (virConnectPtr conn, int *ids, int maxids) +remoteListDomains(virConnectPtr conn, int *ids, int maxids) { int rv = -1; int i; @@ -1274,10 +1266,10 @@ remoteListDomains (virConnectPtr conn, int *ids, int maxids) } args.maxids = maxids; - memset (&ret, 0, sizeof(ret)); - if (call (conn, priv, 0, REMOTE_PROC_LIST_DOMAINS, - (xdrproc_t) xdr_remote_list_domains_args, (char *) &args, - (xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret) == -1) + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, 0, REMOTE_PROC_LIST_DOMAINS, + (xdrproc_t) xdr_remote_list_domains_args, (char *) &args, + (xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret) == -1) goto done; if (ret.ids.ids_len > maxids) { @@ -1293,7 +1285,7 @@ remoteListDomains (virConnectPtr conn, int *ids, int maxids) rv = ret.ids.ids_len; cleanup: - xdr_free ((xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret); + xdr_free((xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret); done: remoteDriverUnlock(priv); -- 1.7.8.6

On 08/03/2012 08:03 AM, Peter Krempa wrote:
Remove spaces before fucntion calls and some other coding nits in some
s/fucntion/function/
parts of the remote driver and refactor getting of URI argument components into variables used by libvirt later on. --- src/remote/remote_driver.c | 292 +++++++++++++++++++++----------------------- 1 files changed, 142 insertions(+), 150 deletions(-)
ACK; reasonable cleanups before the meat of the series.
+ goto no_memory; + + if ((conn->uri && conn->uri->user) &&
The () around the first && is redundant, but doesn't hurt. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/03/12 22:24, Eric Blake wrote:
On 08/03/2012 08:03 AM, Peter Krempa wrote:
Remove spaces before fucntion calls and some other coding nits in some
s/fucntion/function/
parts of the remote driver and refactor getting of URI argument components into variables used by libvirt later on. --- src/remote/remote_driver.c | 292 +++++++++++++++++++++----------------------- 1 files changed, 142 insertions(+), 150 deletions(-)
ACK; reasonable cleanups before the meat of the series.
+ goto no_memory; + + if ((conn->uri && conn->uri->user) &&
The () around the first && is redundant, but doesn't hurt.
I fixed the nits and pushed. I'll post a rebased and version of this set to get virObject support also into this connection driver. Thanks for the review. Peter

This patch adds helper functions to libssh2 that enable us to use libssh2 in conjunction with libvirt-native virNetSockets instead of using a spawned "ssh" client process. This implemetation supports tunneled plaintext, keyboard-interactive, private key, ssh agent based and null authentication. Libvirt's Auth callback is used for interaction with the user. (Keyboard interactive authentication, adding of host keys, private key passphrases). This enables seamless integration into the application using libvirt, as no helpers as "ssh-askpass" are needed. Reading and writing of OpenSSH style "known_hosts" files is supported. Communication is done using SSH exec channel, where the user may specify arbitrary command to be executed on the remote side and reads and writes to/from stdin/out are sent through the ssh channel. Usage of stderr is not supported. As a bonus, this should (untested) add SSH support to libvirt clients running on Windows. --- configure.ac | 40 +- include/libvirt/virterror.h | 2 + po/POTFILES.in | 1 + src/Makefile.am | 9 + src/rpc/virnetlibsshcontext.c | 1444 +++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetlibsshcontext.h | 83 +++ src/util/virterror.c | 8 +- 7 files changed, 1582 insertions(+), 5 deletions(-) create mode 100644 src/rpc/virnetlibsshcontext.c create mode 100644 src/rpc/virnetlibsshcontext.h diff --git a/configure.ac b/configure.ac index 8a04d91..1810979 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,7 @@ OPENWSMAN_REQUIRED="2.2.3" LIBPCAP_REQUIRED="1.0.0" LIBNL_REQUIRED="1.1" LIBSSH2_REQUIRED="1.0" +LIBSSH2_TRANSPORT_REQUIRED="1.3" LIBBLKID_REQUIRED="2.17" DBUS_REQUIRED="1.0.0" @@ -401,6 +402,8 @@ AC_ARG_WITH([console-lock-files], (use auto for default paths on some platforms) @<:@default=auto@:>@]), [],[with_console_lock_files=auto]) +AC_ARG_WITH([libssh2_transport], + AC_HELP_STRING([--with-libssh2_transport], [libssh2 location @<:@default=check@:>@]),[],[with_libssh2_transport=check]) dnl dnl in case someone want to build static binaries @@ -1706,29 +1709,58 @@ AM_CONDITIONAL([WITH_UML], [test "$with_uml" = "yes"]) dnl -dnl check for libssh2 (PHYP) +dnl check for libssh2 (PHYP and libssh2 transport) dnl LIBSSH2_CFLAGS="" LIBSSH2_LIBS="" -if test "$with_phyp" = "yes" || test "$with_phyp" = "check"; then +if test "$with_phyp" = "yes" || test "$with_phyp" = "check" || + test "$with_libssh2_transport" = "yes" || test "$with_libssh2_transport" = "check"; then PKG_CHECK_MODULES([LIBSSH2], [libssh2 >= $LIBSSH2_REQUIRED], [ - with_phyp=yes + if test "$with_phyp" = "check"; then + with_phyp=yes + fi + if $PKG_CONFIG "libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED"; then + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=yes + fi + else + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=no + AC_MSG_NOTICE([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + if test "$with_libssh2_transport" = "yes"; then + AC_MSG_ERROR([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + fi ], [ if test "$with_phyp" = "check"; then with_phyp=no AC_MSG_NOTICE([libssh2 is required for Phyp driver, disabling it]) - else + fi + if test "$with_phyp" = "yes"; then AC_MSG_ERROR([libssh2 >= $LIBSSH2_REQUIRED is required for Phyp driver]) fi + if test "$with_libssh2_transport" = "check"; then + with_libssh2_transport=no + AC_MSG_NOTICE([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi + if test "$with_libssh2_transport" = "yes"; then + AC_MSG_ERROR([libssh2 >= $LIBSSH2_TRANSPORT_REQUIRED is required for libssh2 transport]) + fi ]) fi if test "$with_phyp" = "yes"; then AC_DEFINE_UNQUOTED([WITH_PHYP], 1, [whether IBM HMC / IVM driver is enabled]) fi +if test "$with_libssh2_transport" = "yes"; then + AC_DEFINE_UNQUOTED([HAVE_LIBSSH2], 1, [wether libssh2 transport is enabled]) +fi + AM_CONDITIONAL([WITH_PHYP],[test "$with_phyp" = "yes"]) +AM_CONDITIONAL([HAVE_LIBSSH2], [test "$with_libssh2_transport" = "yes"]) AC_SUBST([LIBSSH2_CFLAGS]) AC_SUBST([LIBSSH2_LIBS]) diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index ad8e101..13a2b9d 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -110,6 +110,7 @@ typedef enum { VIR_FROM_AUTH = 46, /* Error from auth handling */ VIR_FROM_DBUS = 47, /* Error from DBus */ VIR_FROM_PARALLELS = 48, /* Error from Parallels */ + VIR_FROM_LIBSSH = 49, /* Error from libssh2 connection transport */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST @@ -277,6 +278,7 @@ typedef enum { VIR_ERR_MIGRATE_UNSAFE = 81, /* Migration is not safe */ VIR_ERR_OVERFLOW = 82, /* integer overflow */ VIR_ERR_BLOCK_COPY_ACTIVE = 83, /* action prevented by block copy job */ + VIR_ERR_LIBSSH_ERROR = 84, /* error in libssh2 transport driver */ } virErrorNumber; /** diff --git a/po/POTFILES.in b/po/POTFILES.in index 37a00ee..83eab5e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -90,6 +90,7 @@ src/rpc/virkeepalive.c src/rpc/virnetclient.c src/rpc/virnetclientprogram.c src/rpc/virnetclientstream.c +src/rpc/virnetlibsshcontext.c src/rpc/virnetmessage.c src/rpc/virnetsaslcontext.c src/rpc/virnetsocket.c diff --git a/src/Makefile.am b/src/Makefile.am index d4c198e..b49761a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1508,6 +1508,13 @@ libvirt_net_rpc_la_SOURCES = \ rpc/virnettlscontext.h rpc/virnettlscontext.c \ rpc/virkeepaliveprotocol.h rpc/virkeepaliveprotocol.c \ rpc/virkeepalive.h rpc/virkeepalive.c +if HAVE_LIBSSH2 +libvirt_net_rpc_la_SOURCES += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +else +EXTRA_DIST += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +endif if HAVE_SASL libvirt_net_rpc_la_SOURCES += \ rpc/virnetsaslcontext.h rpc/virnetsaslcontext.c @@ -1518,11 +1525,13 @@ endif libvirt_net_rpc_la_CFLAGS = \ $(GNUTLS_CFLAGS) \ $(SASL_CFLAGS) \ + $(LIBSSH2_CFLAGS) \ $(XDR_CFLAGS) \ $(AM_CFLAGS) libvirt_net_rpc_la_LDFLAGS = \ $(GNUTLS_LIBS) \ $(SASL_LIBS) \ + $(LIBSSH2_LIBS)\ $(AM_LDFLAGS) \ $(CYGWIN_EXTRA_LDFLAGS) \ $(MINGW_EXTRA_LDFLAGS) diff --git a/src/rpc/virnetlibsshcontext.c b/src/rpc/virnetlibsshcontext.c new file mode 100644 index 0000000..fa61226 --- /dev/null +++ b/src/rpc/virnetlibsshcontext.c @@ -0,0 +1,1444 @@ +/* + * virnetlibsshcontext.c: libssh network transport provider + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Peter Krempa <pkrempa@redhat.com> + */ +#include <config.h> +#include <libssh2.h> +#include <libssh2_publickey.h> + +#include "virnetlibsshcontext.h" + +#include "internal.h" +#include "buf.h" +#include "memory.h" +#include "logging.h" +#include "configmake.h" +#include "threads.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_LIBSSH + +static const char +vir_libssh2_key_comment[] = "added by libvirt libssh2 transport"; +#define VIR_NET_LIBSSH_BUFFER_SIZE 1024 + +typedef enum { + VIR_NET_LIBSSH_STATE_NEW, + VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE, + VIR_NET_LIBSSH_STATE_AUTH_CALLBACK_ERROR, + VIR_NET_LIBSSH_STATE_CLOSED, + VIR_NET_LIBSSH_STATE_ERROR, + VIR_NET_LIBSSH_STATE_ERROR_REMOTE, +} virNetLibSSHSessionState; + +typedef enum { + VIR_NET_LIBSSH_AUTHCB_OK, + VIR_NET_LIBSSH_AUTHCB_NO_METHOD, + VIR_NET_LIBSSH_AUTHCB_OOM, + VIR_NET_LIBSSH_AUTHCB_RETR_ERR, +} virNetLibSSHAuthCallbackError; + +typedef enum { + VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE, + VIR_NET_LIBSSH_AUTH_PASSWORD, + VIR_NET_LIBSSH_AUTH_PRIVKEY, + VIR_NET_LIBSSH_AUTH_AGENT +} virNetLibSSHAuthMethods; + + +typedef struct _virNetLibSSHAuthMethod virNetLibSSHAuthMethod; +typedef virNetLibSSHAuthMethod *virNetLibSSHAuthMethodPtr; + +struct _virNetLibSSHAuthMethod { + virNetLibSSHAuthMethods method; + const char *username; + const char *password; + const char *filename; + + int tries; +}; + +struct _virNetLibSSHSession { + virNetLibSSHSessionState state; + virMutex lock; + + /* libssh2 internal stuff */ + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + LIBSSH2_KNOWNHOSTS *knownHosts; + LIBSSH2_AGENT *agent; + + /* for host key checking */ + virNetLibSSHHostkeyVerify hostKeyVerify; + const char *knownHostsFile; + const char *hostname; + int port; + + /* authentication stuff */ + virConnectAuthPtr cred; + virNetLibSSHAuthCallbackError authCbErr; + size_t nauths; + virNetLibSSHAuthMethodPtr *auths; + + /* channel stuff */ + const char *channelCommand; + int channelCommandReturnValue; + + /* read cache */ + char rbuf[VIR_NET_LIBSSH_BUFFER_SIZE]; + size_t bufUsed; + size_t bufStart; +}; + +static virNetLibSSHAuthMethodPtr +virNetLibSSHSessionAuthMethodNew(virNetLibSSHSessionPtr sess) +{ + virNetLibSSHAuthMethodPtr auth; + + if (VIR_ALLOC(auth) < 0) + goto error; + + if (VIR_EXPAND_N(sess->auths, sess->nauths, 1) < 0) + goto error; + + sess->auths[sess->nauths - 1] = auth; + + return auth; + +error: + VIR_FREE(auth); + return NULL; +} + +static void +virNetLibSSHSessionAuthMethodsFree(virNetLibSSHSessionPtr sess) +{ + int i; + + for (i = 0; i < sess->nauths; i++) { + VIR_FREE(sess->auths[i]->username); + VIR_FREE(sess->auths[i]->password); + VIR_FREE(sess->auths[i]->filename); + VIR_FREE(sess->auths[i]); + } + + VIR_FREE(sess->auths); + sess->nauths = 0; +} + +/* keyboard interactive authentication callback */ +static void +virNetLibSSHKbIntCb(const char *name ATTRIBUTE_UNUSED, + int name_len ATTRIBUTE_UNUSED, + const char *instruction ATTRIBUTE_UNUSED, + int instruction_len ATTRIBUTE_UNUSED, + int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **opaque) +{ + virNetLibSSHSessionPtr priv = *opaque; + virConnectCredentialPtr askcred = NULL; + int i; + int credtype_echo = -1; + int credtype_noecho = -1; + char *tmp; + + priv->authCbErr = VIR_NET_LIBSSH_AUTHCB_OK; + + /* find credential type for asking passwords */ + for (i = 0; i < priv->cred->ncredtype; i++) { + if (priv->cred->credtype[i] == VIR_CRED_PASSPHRASE || + priv->cred->credtype[i] == VIR_CRED_NOECHOPROMPT) + credtype_noecho = priv->cred->credtype[i]; + + if (priv->cred->credtype[i] == VIR_CRED_ECHOPROMPT) + credtype_echo = priv->cred->credtype[i]; + } + + if (credtype_echo < 0 || credtype_noecho < 0) { + priv->authCbErr = VIR_NET_LIBSSH_AUTHCB_NO_METHOD; + return; + } + + if (VIR_ALLOC_N(askcred, num_prompts) < 0) { + priv->authCbErr = VIR_NET_LIBSSH_AUTHCB_OOM; + return; + } + + /* fill data structures for auth callback */ + for (i = 0; i < num_prompts; i++) { + /* remove colon and trailing spaces from prompts, as default behavior + * of libvirt's auth callback is to add them */ + if ((tmp = strrchr(prompts[i].text, ':'))) + *tmp = '\0'; + + askcred[i].prompt = prompts[i].text; + askcred[i].type = prompts[i].echo ? credtype_echo : credtype_noecho; + } + + /* retrieve responses using the auth callback */ + if (priv->cred->cb(askcred, num_prompts, priv->cred->cbdata)) { + priv->authCbErr = VIR_NET_LIBSSH_AUTHCB_RETR_ERR; + goto cleanup; + } + + /* copy retrieved data back */ + for (i = 0; i < num_prompts; i++) { + responses[i].text = askcred[i].result; + askcred[i].result = NULL; /* steal the pointer */ + responses[i].length = askcred[i].resultlen; + } + +cleanup: + if (askcred) + for (i = 0; i < num_prompts; i++) + VIR_FREE(askcred[i].result); + + VIR_FREE(askcred); + + return; +} + +/* 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, not 0 otherwise + */ +static int +virNetLibSSHCheckHostKey(virNetLibSSHSessionPtr sess) +{ + int ret; + const char *key; + const char *keyhash; + int keyType; + size_t keyLength; + char *errmsg; + virBuffer buff = VIR_BUFFER_INITIALIZER; + virConnectCredential askKey; + struct libssh2_knownhost *knownHostEntry = NULL; + int i; + char *hostnameStr = NULL; + + if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) + return 0; + + /* get the key */ + key = libssh2_session_hostkey(sess->session, &keyLength, &keyType); + if (!key) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("Failed to retrieve ssh host key: %s"), + errmsg); + return -1; + } + + /* verify it */ + ret = libssh2_knownhost_checkp(sess->knownHosts, + sess->hostname, + sess->port, + key, + keyLength, + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW, + &knownHostEntry); + + switch (ret) { + case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: + /* key was not found, query to add it to database */ + if (sess->hostKeyVerify == VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL) { + /* ask to add the key */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%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)); + + for (i = 0; i < sess->cred->ncredtype; i++) { + if (sess->cred->credtype[i] == VIR_CRED_ECHOPROMPT) { + i = -1; + break; + } + } + + if (i > 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("no suitable method to retrieve " + "authentication cretentials")); + return -1; + } + + /* calculate remote key hash, using MD5 algorithm that is + * usual in OpenSSH. The returned value should *NOT* be freed*/ + if (!(keyhash = libssh2_hostkey_hash(sess->session, + LIBSSH2_HOSTKEY_HASH_MD5))) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("failed to calculate ssh host key hash")); + return -1; + } + /* format the host key into a nice userfriendly string. + * Sadly, there's no constant to describe the hash length, so + * we have to use a *MAGIC* constant. */ + for (i = 0; i < 16; i++) { + virBufferAsprintf(&buff, "%02X", (unsigned char) keyhash[i]); + if (i != 15) + virBufferAddChar(&buff, ':'); + } + + if (virBufferError(&buff) != 0) { + virReportOOMError(); + return -1; + } + + keyhash = virBufferContentAndReset(&buff); + + askKey.type = VIR_CRED_ECHOPROMPT; + if (virAsprintf((char **)&askKey.prompt, + _("Accept SSH host key with hash '%s' for " + "host '%s:%d' (%s/%s)?"), + keyhash, + sess->hostname, sess->port, + _("yes"), _("no")) < 0) { + virReportOOMError(); + VIR_FREE(keyhash); + return -1; + } + + if (sess->cred->cb(&askKey, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("failed to retrieve decision to accept " + "host key")); + VIR_FREE(askKey.prompt); + VIR_FREE(keyhash); + return -1; + } + + VIR_FREE(askKey.prompt); + + if (STRNEQ_NULLABLE(askKey.result, _("yes"))) { + virReportError(VIR_ERR_LIBSSH_ERROR, + _("SSH host key for '%s' (%s) was not accepted"), + sess->hostname, keyhash); + VIR_FREE(keyhash); + VIR_FREE(askKey.result); + return -1; + } + VIR_FREE(keyhash); + VIR_FREE(askKey.result); + } + + /* VIR_NET_LIBSSH_HOSTKEY_VERIFY_AUTO_ADD */ + /* convert key type, as libssh is using different enums type for + * getting the key and different for adding ... */ + switch (keyType) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + keyType = LIBSSH2_KNOWNHOST_KEY_SSHRSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + keyType = LIBSSH2_KNOWNHOST_KEY_SSHDSS; + + case LIBSSH2_HOSTKEY_TYPE_UNKNOWN: + default: + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("unsupported SSH key type")); + return -1; + } + + /* add the key to the DB and save it, if applicable */ + /* construct a "[hostname]:port" string to have the hostkey bound + * to port number */ + virBufferAsprintf(&buff, "[%s]:%d", sess->hostname, sess->port); + + if (virBufferError(&buff) != 0) { + virReportOOMError(); + return -1; + } + + hostnameStr = virBufferContentAndReset(&buff); + + if (libssh2_knownhost_addc(sess->knownHosts, + hostnameStr, + NULL, + key, + keyLength, + vir_libssh2_key_comment, + strlen(vir_libssh2_key_comment), + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_RAW | + keyType, + NULL) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("unable to add SSH host key for host '%s': %s"), + hostnameStr, errmsg); + VIR_FREE(hostnameStr); + return -1; + } + + VIR_FREE(hostnameStr); + + /* write the host key file - if applicable */ + if (sess->knownHostsFile) { + if (libssh2_knownhost_writefile(sess->knownHosts, + sess->knownHostsFile, + LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("failed to write known_host file '%s': %s"), + sess->knownHostsFile, + errmsg); + return -1; + } + } + /* key was accepted and added */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MATCH: + /* host key matches */ + return 0; + + case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: + /* 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, + knownHostEntry->key, sess->knownHostsFile); + return -1; + + case LIBSSH2_KNOWNHOST_CHECK_FAILURE: + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("failed to validate SSH host key: %s"), + errmsg); + return -1; + + default: /* should never happen (tm) */ + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", _("Unknown error value")); + return -1; + } + + return -1; +} + +/* perform ssh agent authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetLibSSHAuthenticateAgent(virNetLibSSHSessionPtr sess, + virNetLibSSHAuthMethodPtr priv) +{ + struct libssh2_agent_publickey *agent_identity; + struct libssh2_agent_publickey *agent_prev_identity = NULL; + int ret; + char *errmsg; + + if (libssh2_agent_connect(sess->agent) >= 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to connect to ssh agent")); + return 1; + } + + if (libssh2_agent_list_identities(sess->agent) < 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to list ssh agent identities")); + return 1; + } + + while (!(ret = libssh2_agent_get_identity(sess->agent, + &agent_identity, + agent_prev_identity))) { + if (!(ret = libssh2_agent_userauth(sess->agent, + priv->username, + agent_identity))) + return 0; /* key accepted */ + + if (ret != LIBSSH2_ERROR_AUTHENTICATION_FAILED) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("failed to authenticate using SSH agent: %s"), + errmsg); + return -1; + } + /* authentication has failed, try next key */ + } + + /* if there are no more keys in the agent, the identity retrieval + * function returns > 1 */ + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("failed to authenticate using SSH agent: %s"), + errmsg); + + if (ret < 0) + return -1; + else + return 1; +} + +/* perform private key authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetLibSSHAuthenticatePrivkey(virNetLibSSHSessionPtr sess, + virNetLibSSHAuthMethodPtr priv) +{ + virConnectCredential retr_passphrase; + int i; + char *errmsg; + int ret; + + /* try open the key with no password */ + if ((ret = libssh2_userauth_publickey_fromfile(sess->session, + priv->username, + NULL, + priv->filename, + priv->password)) == 0) + return 0; /* success */ + + if (priv->password || + ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED || + ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication with private key '%s' " + "has failed: %s"), + priv->filename, errmsg); + return 1; /* auth failed */ + } + + /* request user's key password */ + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("No user interaction callback provided: " + "Can't retrieve private key passphrase")); + return -1; + } + + memset(&retr_passphrase, 0, sizeof(virConnectCredential)); + retr_passphrase.type = -1; + + for (i = 0; i < sess->cred->ncredtype; i++) { + if (sess->cred->credtype[i] == VIR_CRED_PASSPHRASE || + sess->cred->credtype[i] == VIR_CRED_NOECHOPROMPT) { + retr_passphrase.type = sess->cred->credtype[i]; + break; + } + } + + if (retr_passphrase.type < 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("no suitable method to retrieve key passphrase")); + return -1; + } + + if (virAsprintf((char **)&retr_passphrase.prompt, + _("Passphrase for key '%s'"), + priv->filename) < 0) { + virReportOOMError(); + return -1; + } + + if (sess->cred->cb(&retr_passphrase, 1, sess->cred->cbdata)) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("failed to retrieve private key passphrase: " + "callback has failed")); + VIR_FREE(retr_passphrase.prompt); + return -1; + } + + VIR_FREE(retr_passphrase.prompt); + + ret = libssh2_userauth_publickey_fromfile(sess->session, + priv->username, + NULL, + priv->filename, + retr_passphrase.result); + + VIR_FREE(retr_passphrase.result); + + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("authentication with private key '%s' " + "has failed: %s"), + priv->filename, errmsg); + + if (ret == LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED || + ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + return 1; + else + return -1; + } + + return 0; +} + +/* perform tunelled password authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetLibSSHAuthenticatePassword(virNetLibSSHSessionPtr sess, + virNetLibSSHAuthMethodPtr priv) +{ + char *errmsg; + int ret; + + /* tunelled password authentication */ + if ((ret = libssh2_userauth_password(sess->session, + priv->username, + priv->password)) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("tunelled password authentication failed: %s"), + errmsg); + + if (ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + return 1; + else + return -1; + } + /* auth success */ + return 0; +} + +/* perform keyboard interactive authentication + * + * Returns: 0 on success + * 1 on authentication failure + * -1 on error + */ +static int +virNetLibSSHAuthenticateKeyboardInteractive(virNetLibSSHSessionPtr sess, + virNetLibSSHAuthMethodPtr priv) +{ + char *errmsg; + int ret; + + if (!sess->cred || !sess->cred->cb) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Can't perform keyboard-interactive authentication: " + "Authentication callback not provided ")); + return -1; + } + + /* try the auth infinite number of times, the server should break the + * connection if maximum number of bad auth tries is exceeded */ + while (priv->tries < 0 || priv->tries-- > 0) { + ret = libssh2_userauth_keyboard_interactive(sess->session, + priv->username, + virNetLibSSHKbIntCb); + + /* check for errors while calling the callback */ + switch (sess->authCbErr) { + case VIR_NET_LIBSSH_AUTHCB_NO_METHOD: + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("no suitable method to retrieve " + "authentication cretentials")); + return -1; + case VIR_NET_LIBSSH_AUTHCB_OOM: + virReportOOMError(); + return -1; + case VIR_NET_LIBSSH_AUTHCB_RETR_ERR: + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("failed to retrieve credentials")); + return -1; + case VIR_NET_LIBSSH_AUTHCB_OK: + /* everything went fine, let's continue */ + break; + } + + if (ret == 0) + /* authentication succeeded */ + return 0; + + if (ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + continue; /* try again */ + + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("keyboard interactive authentication failed: %s"), + errmsg); + return -1; + } + } + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_AUTH_FAILED, + _("keyboard interactive authentication failed: %s"), + errmsg); + return 1; +} + +/* select auth method and authenticate */ +static int +virNetLibSSHAuthenticate(virNetLibSSHSessionPtr sess) +{ + virNetLibSSHAuthMethodPtr auth; + bool no_method = false; + bool auth_failed = false; + char *auth_list; + char *errmsg; + int i; + int ret; + + if (!sess->nauths) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("No authentication methods and credentials " + "provided")); + return -1; + } + + /* obtain list of supported auth methods */ + auth_list = libssh2_userauth_list(sess->session, + sess->auths[0]->username, + strlen(sess->auths[0]->username)); + if (!auth_list) { + /* unlikely event, authentication succeeded with NONE as method */ + if (libssh2_userauth_authenticated(sess->session) == 1) + return 0; + + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("couldn't retrieve authentication methods list: %s"), + errmsg); + return -1; + } + + for (i = 0; i < sess->nauths; i++) { + auth = sess->auths[i]; + + ret = 2; + virResetLastError(); + + switch (auth->method) { + case VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE: + if (strstr(auth_list, "keyboard-interactive")) + ret = virNetLibSSHAuthenticateKeyboardInteractive(sess, auth); + break; + case VIR_NET_LIBSSH_AUTH_AGENT: + if (strstr(auth_list, "publickey")) + ret = virNetLibSSHAuthenticateAgent(sess, auth); + break; + case VIR_NET_LIBSSH_AUTH_PRIVKEY: + if (strstr(auth_list, "publickey")) + ret = virNetLibSSHAuthenticatePrivkey(sess, auth); + break; + case VIR_NET_LIBSSH_AUTH_PASSWORD: + if (strstr(auth_list, "password")) + ret = virNetLibSSHAuthenticatePassword(sess, auth); + break; + } + + /* return on success or error */ + if (ret <= 0) + return ret; + + /* the authentication method is not supported */ + if (ret == 2) + no_method = true; + + /* authentication with this method has failed */ + if (ret == 1) + auth_failed = true; + } + + if (no_method && !auth_failed) { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("None of the requested authentication methods " + "are supported by the server")); + } else { + virReportError(VIR_ERR_AUTH_FAILED, "%s", + _("All provided authentication methods with credentials " + "were rejected by the server")); + } + + return -1; +} + +/* open channel */ +static int +virNetLibSSHOpenChannel(virNetLibSSHSessionPtr sess) +{ + char *errmsg; + + sess->channel = libssh2_channel_open_session(sess->session); + if (!sess->channel) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("failed to open ssh channel: %s"), + errmsg); + return -1; + } + + if (libssh2_channel_exec(sess->channel, sess->channelCommand) != 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("failed to execute command '%s': %s"), + sess->channelCommand, + errmsg); + return -1; + } + + /* nonblocking mode - currently does nothing*/ + libssh2_channel_set_blocking(sess->channel, 0); + + /* channel open */ + return 0; +} + +/* validate if all required parameters are configured */ +static int +virNetLibSSHValidateConfig(virNetLibSSHSessionPtr sess) +{ + if (sess->nauths == 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("No authentication methods and credentials " + "provided")); + return -1; + } + + if (!sess->channelCommand) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("No channel command provided")); + return -1; + } + + if (sess->hostKeyVerify != VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE) { + if (!sess->hostname) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Hostname is needed for host key verification")); + return -1; + } + } + + /* everything ok */ + return 0; +} + +/* ### PUBLIC API ### */ +int +virNetLibSSHSessionAuthSetCallback(virNetLibSSHSessionPtr sess, + virConnectAuthPtr auth) +{ + virMutexLock(&sess->lock); + sess->cred = auth; + virMutexUnlock(&sess->lock); + return 0; +} + +void +virNetLibSSHSessionAuthReset(virNetLibSSHSessionPtr sess) +{ + virMutexLock(&sess->lock); + virNetLibSSHSessionAuthMethodsFree(sess); + virMutexUnlock(&sess->lock); +} + +int +virNetLibSSHSessionAuthAddPasswordAuth(virNetLibSSHSessionPtr sess, + const char *username, + const char *password) +{ + virNetLibSSHAuthMethodPtr auth; + char *user = NULL; + char *pass = NULL; + + if (!username || !password) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Username and password must be provided " + "for password authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username)) || + !(pass = strdup(password))) + goto no_memory; + + if (!(auth = virNetLibSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->password = pass; + auth->method = VIR_NET_LIBSSH_AUTH_PASSWORD; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + VIR_FREE(pass); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetLibSSHSessionAuthAddAgentAuth(virNetLibSSHSessionPtr sess, + const char *username) +{ + virNetLibSSHAuthMethodPtr auth; + char *user = NULL; + + if (!username) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Username must be provided " + "for ssh agent authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username))) + goto no_memory; + + + if (!(auth = virNetLibSSHSessionAuthMethodNew(sess))) { + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + } + + auth->username = user; + auth->method = VIR_NET_LIBSSH_AUTH_AGENT; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetLibSSHSessionAuthAddPrivKeyAuth(virNetLibSSHSessionPtr sess, + const char *username, + const char *keyfile, + const char *password) +{ + virNetLibSSHAuthMethodPtr auth; + + char *user = NULL; + char *pass = NULL; + char *file = NULL; + + if (!username || !keyfile) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Username and key file path must be provided " + "for private key authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username)) || + !(file = strdup(keyfile))) + goto no_memory; + + if (password) { + if (!(pass = strdup(password))) + goto no_memory; + } + + if (!(auth = virNetLibSSHSessionAuthMethodNew(sess))) + goto no_memory; + + auth->username = user; + auth->password = pass; + auth->filename = file; + auth->method = VIR_NET_LIBSSH_AUTH_PRIVKEY; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + VIR_FREE(pass); + VIR_FREE(file); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; +} + +int +virNetLibSSHSessionAuthAddKeyboardAuth(virNetLibSSHSessionPtr sess, + const char *username, + int tries) +{ + virNetLibSSHAuthMethodPtr auth; + char *user = NULL; + + if (!username) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Username must be provided " + "for ssh agent authentication")); + return -1; + } + + virMutexLock(&sess->lock); + + if (!(user = strdup(username))) + goto no_memory; + + + if (!(auth = virNetLibSSHSessionAuthMethodNew(sess))) { + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + } + + auth->username = user; + auth->tries = tries; + auth->method = VIR_NET_LIBSSH_AUTH_KEYBOARD_INTERACTIVE; + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + VIR_FREE(user); + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + +} + +int +virNetLibSSHSessionSetChannelCommand(virNetLibSSHSessionPtr sess, + const char *command) +{ + virMutexLock(&sess->lock); + + VIR_FREE(sess->channelCommand); + + if (command && !(sess->channelCommand = strdup(command))) { + virReportOOMError(); + virMutexUnlock(&sess->lock); + return -1; + } + + virMutexUnlock(&sess->lock); + return 0; +} + +int +virNetLibSSHSessionSetHostKeyVerification(virNetLibSSHSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + bool readonly, + virNetLibSSHHostkeyVerify opt) +{ + char *errmsg; + + virMutexLock(&sess->lock); + + sess->port = port; + sess->hostKeyVerify = opt; + + VIR_FREE(sess->hostname); + + if (hostname && !(sess->hostname = strdup(hostname))) + goto no_memory; + + /* load the known hosts file */ + if (hostsfile) { + if (libssh2_knownhost_readfile(sess->knownHosts, + hostsfile, + LIBSSH2_KNOWNHOST_FILE_OPENSSH) < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("unable to load knownhosts file '%s': %s"), + hostsfile, errmsg); + } + + /* set filename only if writing to the known hosts file is requested */ + + if (!readonly) { + VIR_FREE(sess->knownHostsFile); + if ((sess->knownHostsFile = strdup(hostsfile)) == NULL) + goto no_memory; + } + } + + virMutexUnlock(&sess->lock); + return 0; + +no_memory: + virMutexUnlock(&sess->lock); + virReportOOMError(); + return -1; +} + +/* allocate and initialize a ssh session object */ +virNetLibSSHSessionPtr virNetLibSSHSessionNew(void) +{ + virNetLibSSHSessionPtr sess; + if (VIR_ALLOC(sess) < 0) + return NULL; + + /* initialize internal structures */ + if (virMutexInit(&sess->lock) < 0) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to initialize mutex")); + goto error; + } + + /* initialize session data, use the internal data for callbacks + * and stick to default memory management functions */ + if (!(sess->session = libssh2_session_init_ex(NULL, + NULL, + NULL, + (void *)sess))) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to initialize libssh2 session")); + goto error; + } + + if (!(sess->knownHosts = libssh2_knownhost_init(sess->session))) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to initialize libssh2 known hosts table")); + goto error; + } + + if (!(sess->agent = libssh2_agent_init(sess->session))) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Failed to initialize libssh2 agent handle")); + goto error; + } + + VIR_DEBUG("virNetLibSSHSessionPtr=0x%p, LIBSSH2_SESSION *=0x%p", + sess, + sess->session); + + /* set blocking mode for libssh2 until handshake is complete */ + libssh2_session_set_blocking(sess->session, 1); + + /* default states for config variables */ + sess->state = VIR_NET_LIBSSH_STATE_NEW; + sess->hostKeyVerify = VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE; + + return sess; + +error: + libssh2_agent_free(sess->agent); + libssh2_knownhost_free(sess->knownHosts); + libssh2_session_free(sess->session); + VIR_FREE(sess); + return NULL; +} + +/* close session and free internal data */ +void +virNetLibSSHSessionFree(virNetLibSSHSessionPtr sess) +{ + VIR_DEBUG("sess=0x%p", sess); + + if (!sess) + return; + + virMutexLock(&sess->lock); + + if (sess->channel) { + libssh2_channel_send_eof(sess->channel); + libssh2_channel_close(sess->channel); + libssh2_channel_free(sess->channel); + } + + libssh2_knownhost_free(sess->knownHosts); + libssh2_agent_free(sess->agent); + + if (sess->session) { + libssh2_session_disconnect(sess->session, + "libvirt: virNetLibSSHSessionFree()"); + libssh2_session_free(sess->session); + } + + virNetLibSSHSessionAuthMethodsFree(sess); + + VIR_FREE(sess->channelCommand); + VIR_FREE(sess->hostname); + VIR_FREE(sess->knownHostsFile); + + VIR_FREE(sess); +} + +int +virNetLibSSHSessionConnect(virNetLibSSHSessionPtr sess, + int sock) +{ + int ret; + char *errmsg; + + VIR_DEBUG("sess=0x%p, sock=%d", sess, sock); + + if (!sess || sess->state != VIR_NET_LIBSSH_STATE_NEW) { + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Invalid virNetLibSSHSessionPtr")); + return -1; + } + + virMutexLock(&sess->lock); + + /* check if configuration is valid */ + if ((ret = virNetLibSSHValidateConfig(sess)) < 0) + goto error; + + /* open session */ + ret = libssh2_session_handshake(sess->session, sock); + /* libssh2 is in blocking mode, so EAGAIN will never happen */ + if (ret < 0) { + libssh2_session_last_error(sess->session, &errmsg, NULL, 0); + 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 */ + libssh2_session_set_blocking(sess->session, 0); + sess->state = VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE; + + virMutexUnlock(&sess->lock); + return ret; + +error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + 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) +{ + ssize_t ret = -1; + ssize_t read_n = 0; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_LIBSSH_ERROR, + _("Remote program terminated with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Tried to write socket in error state")); + + virMutexUnlock(&sess->lock); + 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 = libssh2_channel_read(sess->channel, + buf + read_n, + len - read_n); + + if (ret == LIBSSH2_ERROR_EAGAIN) + goto success; + + if (ret < 0) + goto error; + + read_n += ret; + } + + /* try to read something into the internal buffer */ + if (sess->bufUsed == 0) { + ret = libssh2_channel_read(sess->channel, + sess->rbuf, + VIR_NET_LIBSSH_BUFFER_SIZE); + + if (ret == LIBSSH2_ERROR_EAGAIN) + 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 = libssh2_channel_read_stderr(sess->channel, + sess->rbuf, + VIR_NET_LIBSSH_BUFFER_SIZE - 1); + if (ret > 0) { + sess->rbuf[ret] = '\0'; + VIR_DEBUG("flushing stderr, data='%s'", sess->rbuf); + } + } + + if (libssh2_channel_eof(sess->channel)) { + if (libssh2_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_LIBSSH_ERROR, + _("Remote command terminated with non-zero code: %d"), + libssh2_channel_get_exit_status(sess->channel)); + sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel); + sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE; + virMutexUnlock(&sess->lock); + return -1; + } + + sess->state = VIR_NET_LIBSSH_STATE_CLOSED; + virMutexUnlock(&sess->lock); + return -1; + } + +success: + virMutexUnlock(&sess->lock); + return read_n; + +error: + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + virMutexUnlock(&sess->lock); + return ret; +} + +ssize_t +virNetLibSSHChannelWrite(virNetLibSSHSessionPtr sess, + const char *buf, + size_t len) +{ + ssize_t ret; + + virMutexLock(&sess->lock); + + if (sess->state != VIR_NET_LIBSSH_STATE_HANDSHAKE_COMPLETE) { + if (sess->state == VIR_NET_LIBSSH_STATE_ERROR_REMOTE) + virReportError(VIR_ERR_LIBSSH_ERROR, + _("Remote program terminated with non-zero code: %d"), + sess->channelCommandReturnValue); + else + virReportError(VIR_ERR_LIBSSH_ERROR, "%s", + _("Tried to write socket in error state")); + ret = -1; + goto cleanup; + } + + if (libssh2_channel_eof(sess->channel)) { + if (libssh2_channel_get_exit_status(sess->channel)) { + virReportError(VIR_ERR_LIBSSH_ERROR, + _("Remote program terminated with non-zero code: %d"), + libssh2_channel_get_exit_status(sess->channel)); + sess->state = VIR_NET_LIBSSH_STATE_ERROR_REMOTE; + sess->channelCommandReturnValue = libssh2_channel_get_exit_status(sess->channel); + + ret = -1; + goto cleanup; + } + + sess->state = VIR_NET_LIBSSH_STATE_CLOSED; + ret = -1; + goto cleanup; + } + + ret = libssh2_channel_write(sess->channel, buf, len); + if (ret == LIBSSH2_ERROR_EAGAIN) { + ret = 0; + goto cleanup; + } + + if (ret < 0) { + char *msg; + sess->state = VIR_NET_LIBSSH_STATE_ERROR; + libssh2_session_last_error(sess->session, &msg, NULL, 0); + virReportError(VIR_ERR_LIBSSH_ERROR, + _("write failed: %s"), msg); + } + +cleanup: + virMutexUnlock(&sess->lock); + return ret; +} + +bool virNetLibSSHHasCachedData(virNetLibSSHSessionPtr sess) +{ + bool ret; + + if (!sess) + return false; + + virMutexLock(&sess->lock); + + ret = sess->bufUsed > 0; + + virMutexUnlock(&sess->lock); + return ret; +} diff --git a/src/rpc/virnetlibsshcontext.h b/src/rpc/virnetlibsshcontext.h new file mode 100644 index 0000000..1fe90ed --- /dev/null +++ b/src/rpc/virnetlibsshcontext.h @@ -0,0 +1,83 @@ +/* + * virnetlibsshcontext.h: libssh transport provider + * + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Peter Krempa <pkrempa@redhat.com> + */ +#ifndef __VIR_NET_LIBSSH_CONTEXT_H__ +# define __VIR_NET_LIBSSH_CONTEXT_H__ + +# include "internal.h" + +typedef struct _virNetLibSSHSession virNetLibSSHSession; +typedef virNetLibSSHSession *virNetLibSSHSessionPtr; + +virNetLibSSHSessionPtr virNetLibSSHSessionNew(void); +void virNetLibSSHSessionFree(virNetLibSSHSessionPtr sess); + +typedef enum { + VIR_NET_LIBSSH_HOSTKEY_VERIFY_NORMAL, + VIR_NET_LIBSSH_HOSTKEY_VERIFY_AUTO_ADD, + VIR_NET_LIBSSH_HOSTKEY_VERIFY_IGNORE +} virNetLibSSHHostkeyVerify; + +int virNetLibSSHSessionSetChannelCommand(virNetLibSSHSessionPtr sess, + const char *command); + +void virNetLibSSHSessionAuthReset(virNetLibSSHSessionPtr sess); + +int virNetLibSSHSessionAuthSetCallback(virNetLibSSHSessionPtr sess, + virConnectAuthPtr auth); + +int virNetLibSSHSessionAuthAddPasswordAuth(virNetLibSSHSessionPtr sess, + const char *username, + const char *password); + +int virNetLibSSHSessionAuthAddAgentAuth(virNetLibSSHSessionPtr sess, + const char *username); + +int virNetLibSSHSessionAuthAddPrivKeyAuth(virNetLibSSHSessionPtr sess, + const char *username, + const char *keyfile, + const char *password); + +int virNetLibSSHSessionAuthAddKeyboardAuth(virNetLibSSHSessionPtr sess, + const char *username, + int tries); + +int virNetLibSSHSessionSetHostKeyVerification(virNetLibSSHSessionPtr sess, + const char *hostname, + int port, + const char *hostsfile, + bool readonly, + virNetLibSSHHostkeyVerify opt); + +int virNetLibSSHSessionConnect(virNetLibSSHSessionPtr sess, + int sock); + +ssize_t virNetLibSSHChannelRead(virNetLibSSHSessionPtr sess, + char *buf, + size_t len); + +ssize_t virNetLibSSHChannelWrite(virNetLibSSHSessionPtr sess, + const char *buf, + size_t len); + +bool virNetLibSSHHasCachedData(virNetLibSSHSessionPtr sess); + +#endif /* __VIR_NET_LIBSSH_CONTEXT_H__ */ diff --git a/src/util/virterror.c b/src/util/virterror.c index a40cfe0..586ac0f 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -112,7 +112,8 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "URI Utils", /* 45 */ "Authentication Utils", "DBus Utils", - "Parallels Cloud Server" + "Parallels Cloud Server", + "LibSSH2 transport" ) @@ -1185,6 +1186,11 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("block copy still active: %s"); break; + case VIR_ERR_LIBSSH_ERROR: + if (info == NULL) + errmsg = _("LibSSH2 transport error"); + else + errmsg = _("LibSSH2 transport error: %s"); } return errmsg; } -- 1.7.8.6

On 08/03/2012 08:03 AM, Peter Krempa wrote:
This patch adds helper functions to libssh2 that enable us to use libssh2 in conjunction with libvirt-native virNetSockets instead of using a spawned "ssh" client process.
This implemetation supports tunneled plaintext, keyboard-interactive,
s/implemtation/implementation/
private key, ssh agent based and null authentication. Libvirt's Auth callback is used for interaction with the user. (Keyboard interactive authentication, adding of host keys, private key passphrases). This enables seamless integration into the application using libvirt, as no helpers as "ssh-askpass" are needed.
Reading and writing of OpenSSH style "known_hosts" files is supported.
Communication is done using SSH exec channel, where the user may specify arbitrary command to be executed on the remote side and reads and writes to/from stdin/out are sent through the ssh channel. Usage of stderr is not supported.
As a bonus, this should (untested) add SSH support to libvirt clients running on Windows.
if test "$with_phyp" = "yes"; then AC_DEFINE_UNQUOTED([WITH_PHYP], 1, [whether IBM HMC / IVM driver is enabled]) fi +if test "$with_libssh2_transport" = "yes"; then + AC_DEFINE_UNQUOTED([HAVE_LIBSSH2], 1, [wether libssh2 transport is enabled])
s/wether/whether/
+++ b/src/Makefile.am @@ -1508,6 +1508,13 @@ libvirt_net_rpc_la_SOURCES = \ rpc/virnettlscontext.h rpc/virnettlscontext.c \ rpc/virkeepaliveprotocol.h rpc/virkeepaliveprotocol.c \ rpc/virkeepalive.h rpc/virkeepalive.c +if HAVE_LIBSSH2 +libvirt_net_rpc_la_SOURCES += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +else +EXTRA_DIST += \ + rpc/virnetlibsshcontext.h rpc/virnetlibsshcontext.c +endif
Doing it this way requires a separate .syms file. How hard would it be to provide stub symbols, so that we always compile and link against the file, but the file is a no-op when HAVE_LIBSSH2 is not available?
+++ b/src/rpc/virnetlibsshcontext.c
+/* keyboard interactive authentication callback */ +static void +virNetLibSSHKbIntCb(const char *name ATTRIBUTE_UNUSED, + int name_len ATTRIBUTE_UNUSED, + const char *instruction ATTRIBUTE_UNUSED, + int instruction_len ATTRIBUTE_UNUSED, + int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **opaque) +{
prompts is marked 'const', but...
+ /* fill data structures for auth callback */ + for (i = 0; i < num_prompts; i++) { + /* remove colon and trailing spaces from prompts, as default behavior + * of libvirt's auth callback is to add them */ + if ((tmp = strrchr(prompts[i].text, ':'))) + *tmp = '\0';
...you are modifying it in-place by using strrchr to cast away const. Is that an issue where you could cause a SEGV, and should be strdup'ing the prompt before modifying it?
+/* 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, not 0 otherwise + */ +static int +virNetLibSSHCheckHostKey(virNetLibSSHSessionPtr sess)
Is the return always negative on error?
+ /* format the host key into a nice userfriendly string. + * Sadly, there's no constant to describe the hash length, so + * we have to use a *MAGIC* constant. */ + for (i = 0; i < 16; i++) { + virBufferAsprintf(&buff, "%02X", (unsigned char) keyhash[i]);
Is the cast there to avoid unintentional sign-extension because keyhash[i] might be signed? If so, you could avoid the cast by telling asprintf that you are passing 1 byte in the first place with the 'hh' modifier.
+ if (i != 15) + virBufferAddChar(&buff, ':'); + }
Slightly more efficient to use: for (i = 0; i < 16; i++) virBufferAsprintf(&buff, "%02hhX:", keyhash[i]); virBufferTrim(&buff, ":", 1);
+ + if (STRNEQ_NULLABLE(askKey.result, _("yes"))) { + virReportError(VIR_ERR_LIBSSH_ERROR, + _("SSH host key for '%s' (%s) was not accepted"),
When parsing user input, don't you want a case-insensitive comparison? Also, what about users that want to type 'y' instead of 'yes'? And is comparing to a translated string wise? Without consulting LC_MESSAGES for the proper regex for the user's chosen locale, I'd almost feel safer with a check hard-coded to English 'y' and 'n' rather than to the arbitrary language _("yes"). I'm not quite sure how I would test all of the code, but the bulk of it looked sane by just glancing over it. Having not specifically coded with libssh2, I can't say if you were using the library API correctly without spending a lot longer on the review; but if it is possible to easily test the results, that would go a long way to convince me that the code itself is doing the right thing. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/06/12 22:46, Eric Blake wrote:
I'm not quite sure how I would test all of the code, but the bulk of it looked sane by just glancing over it. Having not specifically coded with libssh2, I can't say if you were using the library API correctly without spending a lot longer on the review; but if it is possible to easily test the results, that would go a long way to convince me that the code itself is doing the right thing.
The best way to test this is to apply also the other patches from this set and try to use libvirt with this transport. I'll send a v2 of this set, that will contain fixes to your comments and I'll try to integrate it with danpb's virObjects. Also one of the developers of libssh2 asked me to call the transport with the 2 at the end (eg. qemu+libssh2://user@host/system ) to avoid confusion with libssh, which is a different project. Peter

This patch enables virNetSocket to be used as an ssh client when properly configured. Fucntion virNetSocketNewConnectLibSSH() is added, that takes all needed parameters and creates a libssh2 session context and performs steps needed to open the connection. --- src/libvirt_private.syms | 1 + src/rpc/virnetsocket.c | 178 +++++++++++++++++++++++++++++++++++++++++++++- src/rpc/virnetsocket.h | 13 ++++ 3 files changed, 191 insertions(+), 1 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 75997fe..e5d8a5e 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1600,6 +1600,7 @@ virNetSocketListen; virNetSocketLocalAddrString; virNetSocketNewConnectCommand; virNetSocketNewConnectExternal; +virNetSocketNewConnectLibSSH; virNetSocketNewConnectSSH; virNetSocketNewConnectTCP; virNetSocketNewConnectUNIX; diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index 88e5525..b841513 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -47,6 +47,10 @@ #include "passfd.h" +#if HAVE_LIBSSH2 +# include "virnetlibsshcontext.h" +#endif + #define VIR_FROM_THIS VIR_FROM_RPC @@ -82,6 +86,9 @@ struct _virNetSocket { size_t saslEncodedLength; size_t saslEncodedOffset; #endif +#if HAVE_LIBSSH2 + virNetLibSSHSessionPtr sshSession; +#endif }; @@ -688,6 +695,139 @@ int virNetSocketNewConnectSSH(const char *nodename, return virNetSocketNewConnectCommand(cmd, retsock); } +#if HAVE_LIBSSH2 +int +virNetSocketNewConnectLibSSH(const char *host, + const char *port, + const char *username, + const char *password, + const char *privkey, + const char *knownHosts, + const char *knownHostsVerify, + const char *authMethods, + const char *command, + virConnectAuthPtr auth, + 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_ERROR, "%s", + _("Failed to parse port number")); + goto error; + } + + /* create ssh session context */ + if (!(sess = virNetLibSSHSessionNew())) + 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, + false, + verify) != 0) + goto error; + + if (virNetLibSSHSessionSetChannelCommand(sess, command) != 0) + goto error; + + if (!(authMethodNext = authMethodsCopy = strdup(authMethods))) { + virReportOOMError(); + goto error; + } + + while ((authMethod = strsep(&authMethodNext, ","))) { + if (STRCASEEQ(authMethod, "keyboard-interactive")) + ret = virNetLibSSHSessionAuthAddKeyboardAuth(sess, username, -1); + else if (STRCASEEQ(authMethod, "password")) + ret = virNetLibSSHSessionAuthAddPasswordAuth(sess, + username, + password); + else if (STRCASEEQ(authMethod, "privkey")) + ret = virNetLibSSHSessionAuthAddPrivKeyAuth(sess, + username, + privkey, + NULL); + else if (STRCASEEQ(authMethod, "agent")) + ret = virNetLibSSHSessionAuthAddAgentAuth(sess, username); + 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, &sock)) < 0) + goto error; + + /* connect to the host using ssh */ + if ((ret = virNetLibSSHSessionConnect(sess, virNetSocketGetFD(sock))) != 0) + goto error; + + sock->sshSession = sess; + *retsock = sock; + + VIR_FREE(authMethodsCopy); + return 0; + +error: + virNetSocketFree(sock); + virNetLibSSHSessionFree(sess); + VIR_FREE(authMethodsCopy); + return ret; +} +#else +int +virNetSocketNewConnectLibSSH(const char *host ATTRIBUTE_UNUSED, + const char *port ATTRIBUTE_UNUSED, + const char *username ATTRIBUTE_UNUSED, + const char *password 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, + virNetSocketPtr *retsock ATTRIBUTE_UNUSED) +{ + virReportSystemError(ENOSYS, "%s", + _("libssh2 transport support was not enabled")); + return -1; +} +#endif /* HAVE_LIBSSH2 */ int virNetSocketNewConnectExternal(const char **cmdargv, virNetSocketPtr *retsock) @@ -753,6 +893,10 @@ void virNetSocketFree(virNetSocketPtr sock) virNetSASLSessionFree(sock->saslSession); #endif +#if HAVE_LIBSSH2 + virNetLibSSHSessionFree(sock->sshSession); +#endif + VIR_FORCE_CLOSE(sock->fd); VIR_FORCE_CLOSE(sock->errfd); @@ -937,6 +1081,12 @@ bool virNetSocketHasCachedData(virNetSocketPtr sock ATTRIBUTE_UNUSED) { bool hasCached = false; virMutexLock(&sock->lock); + +#if HAVE_LIBSSH2 + if (virNetLibSSHHasCachedData(sock->sshSession)) + hasCached = true; +#endif + #if HAVE_SASL if (sock->saslDecoded) hasCached = true; @@ -945,6 +1095,21 @@ bool virNetSocketHasCachedData(virNetSocketPtr sock ATTRIBUTE_UNUSED) return hasCached; } +#if HAVE_LIBSSH2 +static ssize_t virNetSocketLibSSHRead(virNetSocketPtr sock, + char *buf, + size_t len) +{ + return virNetLibSSHChannelRead(sock->sshSession, buf, len); +} + +static ssize_t virNetSocketLibSSHWrite(virNetSocketPtr sock, + const char *buf, + size_t len) +{ + return virNetLibSSHChannelWrite(sock->sshSession, buf, len); +} +#endif bool virNetSocketHasPendingData(virNetSocketPtr sock ATTRIBUTE_UNUSED) { @@ -963,6 +1128,12 @@ static ssize_t virNetSocketReadWire(virNetSocketPtr sock, char *buf, size_t len) { char *errout = NULL; ssize_t ret; + +#if HAVE_LIBSSH2 + if (sock->sshSession) + return virNetSocketLibSSHRead(sock, buf, len); +#endif + reread: if (sock->tlsSession && virNetTLSSessionGetHandshakeStatus(sock->tlsSession) == @@ -1012,6 +1183,12 @@ reread: static ssize_t virNetSocketWriteWire(virNetSocketPtr sock, const char *buf, size_t len) { ssize_t ret; + +#if HAVE_LIBSSH2 + if (sock->sshSession) + return virNetSocketLibSSHWrite(sock, buf, len); +#endif + rewrite: if (sock->tlsSession && virNetTLSSessionGetHandshakeStatus(sock->tlsSession) == @@ -1140,7 +1317,6 @@ static ssize_t virNetSocketWriteSASL(virNetSocketPtr sock, const char *buf, size } #endif - ssize_t virNetSocketRead(virNetSocketPtr sock, char *buf, size_t len) { ssize_t ret; diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h index 6c8e77c..adbdacf 100644 --- a/src/rpc/virnetsocket.h +++ b/src/rpc/virnetsocket.h @@ -74,6 +74,18 @@ int virNetSocketNewConnectSSH(const char *nodename, const char *path, virNetSocketPtr *addr); +int virNetSocketNewConnectLibSSH(const char *host, + const char *port, + const char *username, + const char *password, + const char *privkey, + const char *knownHosts, + const char *knownHostsVerify, + const char *authMethods, + const char *command, + virConnectAuthPtr auth, + virNetSocketPtr *retsock); + int virNetSocketNewConnectExternal(const char **cmdargv, virNetSocketPtr *addr); @@ -102,6 +114,7 @@ int virNetSocketRecvFD(virNetSocketPtr sock, int *fd); void virNetSocketSetTLSSession(virNetSocketPtr sock, virNetTLSSessionPtr sess); + # ifdef HAVE_SASL void virNetSocketSetSASLSession(virNetSocketPtr sock, virNetSASLSessionPtr sess); -- 1.7.8.6

This patch adds a glue layer to enable using libssh2 code with the network client code. As in the original client implementation, shell code is sent to the server to detect correct options for netcat. --- src/rpc/virnetclient.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++- src/rpc/virnetclient.h | 14 +++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c index b210a72..625aa6e 100644 --- a/src/rpc/virnetclient.c +++ b/src/rpc/virnetclient.c @@ -1,7 +1,7 @@ /* * virnetclient.c: generic network RPC client * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2012 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 @@ -36,6 +36,7 @@ #include "logging.h" #include "util.h" #include "virterror_internal.h" +#include "virnetlibsshcontext.h" #define VIR_FROM_THIS VIR_FROM_RPC @@ -411,6 +412,123 @@ virNetClientPtr virNetClientNewSSH(const char *nodename, return virNetClientNew(sock, NULL); } +#define DEFAULT_VALUE(VAR, VAL) \ + if (!VAR) \ + VAR = VAL; +virNetClientPtr virNetClientNewLibSSH(const char *host, + const char *port, + const char *username, + const char *password, + const char *privkeyPath, + const char *knownHostsPath, + const char *knownHostsVerify, + const char *authMethods, + const char *netcatPath, + const char *socketPath, + virConnectAuthPtr authPtr) +{ + virNetSocketPtr sock = NULL; + virNetClientPtr ret = NULL; + + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *nc = NULL; + char *command = NULL; + + char *homedir = virGetUserDirectory(); + char *knownhosts = NULL; + char *privkey = NULL; + + /* Use default paths for known hosts an public keys if not provided */ + if (homedir) { + if (!knownHostsPath) { + virBufferAsprintf(&buf, "%s/.ssh/known_hosts", homedir); + if (!(knownhosts = virBufferContentAndReset(&buf))) + goto no_memory; + + if (!(virFileExists(knownhosts))) + VIR_FREE(knownhosts); + } else { + if (!(knownhosts = strdup(knownHostsPath))) + goto no_memory; + } + + if (!privkeyPath) { + /* RSA */ + virBufferAsprintf(&buf, "%s/.ssh/id_rsa", homedir); + if (!(privkey = virBufferContentAndReset(&buf))) + goto no_memory; + + if (!(virFileExists(privkey))) + VIR_FREE(privkey); + /* DSA */ + if (!privkey) { + virBufferAsprintf(&buf, "%s/.ssh/id_dsa", homedir); + if (!(privkey = virBufferContentAndReset(&buf))) + goto no_memory; + + if (!(virFileExists(privkey))) + VIR_FREE(privkey); + } + } else { + if (!(privkey = strdup(privkeyPath))) + goto no_memory; + } + } + + if (!authMethods) { + if (privkey) + authMethods = "agent,privkey,keyboard-interactive"; + else + authMethods = "agent,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, + "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; + + if (virNetSocketNewConnectLibSSH(host, port, username, password, privkey, + knownhosts, knownHostsVerify, authMethods, + command, authPtr, &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(nc); + virNetSocketFree(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 c939475..b6edd74 100644 --- a/src/rpc/virnetclient.h +++ b/src/rpc/virnetclient.h @@ -1,7 +1,7 @@ /* * virnetclient.h: generic network RPC client * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2012 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 @@ -49,6 +49,18 @@ virNetClientPtr virNetClientNewSSH(const char *nodename, const char *keyfile, const char *path); +virNetClientPtr virNetClientNewLibSSH(const char *host, + const char *port, + const char *username, + const char *password, + const char *privkeyPath, + const char *knownHostsPath, + const char *knownHostsVerify, + const char *authMethods, + const char *netcatPath, + const char *socketPath, + virConnectAuthPtr authPtr); + virNetClientPtr virNetClientNewExternal(const char **cmdargv); typedef void (*virNetClientCloseFunc)(virNetClientPtr client, -- 1.7.8.6

This patch adds URI options to support libssh2 transport in the remote driver. A new transport sceme is introduced eg. "qemu+libssh://..." that utilizes the libssh2 code added in previous patches. The libssh2 code requires the authentication callback to be able to perform keyboard-interactive authentication or to ask t passprhases or add host keys to known hosts database. Added URI components: - known_hosts - path to a knownHosts file in OpenSSH format to check for known ssh host keys - known_hosts_verify - how to deal with server key verification: * "normal" (default) - ask to add new keys * "auto" - automaticaly add new keys * "ignore" - don't validate host keys - auth - authentication methods to use. Default is "agent,privkey,keyboard-interactive". It's a comma separated string of methods to try while authenticating. The order is preserved. Some of the methods may require additional parameters. - password - Password for password authentication. Locations of the known_hosts file and private keys are set to default values if they're present. (~/.ssh/known_hosts, ~/.ssh/id_rsa, ~/.ssh/id_dsa) --- src/remote/remote_driver.c | 47 ++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index f643bbe..443fca0 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -385,6 +385,8 @@ static void remoteClientCloseFunc(virNetClientPtr client ATTRIBUTE_UNUSED, * - xxx+tcp:/// -> TCP connection to localhost * - xxx+unix:/// -> UNIX domain socket * - xxx:/// -> UNIX domain socket + * - xxx+ssh:/// -> SSH connection (legacy) + * - xxx+libssh:/// -> SSH connection (using libssh2) */ static int doRemoteOpen(virConnectPtr conn, @@ -397,6 +399,7 @@ doRemoteOpen(virConnectPtr conn, trans_tls, trans_unix, trans_ssh, + trans_libssh, trans_ext, trans_tcp, } transport; @@ -439,6 +442,8 @@ doRemoteOpen(virConnectPtr conn, } } else if (STRCASEEQ(transport_str, "ssh")) transport = trans_ssh; + else if (STRCASEEQ(transport_str, "libssh")) + transport = trans_libssh; else if (STRCASEEQ(transport_str, "ext")) transport = trans_ext; else if (STRCASEEQ(transport_str, "tcp")) @@ -446,7 +451,7 @@ doRemoteOpen(virConnectPtr conn, else { virReportError(VIR_ERR_INVALID_ARG, "%s", _("remote_open: transport in URL not recognised " - "(should be tls|unix|ssh|ext|tcp)")); + "(should be tls|unix|ssh|ext|tcp|libssh)")); return VIR_DRV_OPEN_ERROR; } } @@ -460,10 +465,12 @@ doRemoteOpen(virConnectPtr conn, * get freed in the failed: path. */ char *name = NULL, *command = NULL, *sockname = NULL, *netcat = NULL; - char *port = NULL, *authtype = NULL, *username = NULL; + char *port = NULL, *authtype = NULL, *username = NULL, *password = NULL; bool sanity = true, verify = true, tty ATTRIBUTE_UNUSED = true; char *pkipath = NULL, *keyfile = NULL; + char *knownHostsVerify = NULL, *knownHosts = NULL; + /* Return code from this function, and the private data. */ int retcode = VIR_DRV_OPEN_ERROR; @@ -508,6 +515,9 @@ doRemoteOpen(virConnectPtr conn, EXTRACT_URI_ARG_STR("netcat", netcat); EXTRACT_URI_ARG_STR("keyfile", keyfile); EXTRACT_URI_ARG_STR("pkipath", pkipath); + EXTRACT_URI_ARG_STR("known_hosts", knownHosts); + EXTRACT_URI_ARG_STR("known_hosts_verify", knownHostsVerify); + EXTRACT_URI_ARG_STR("password", password); EXTRACT_URI_ARG_BOOL("no_sanity", sanity); EXTRACT_URI_ARG_BOOL("no_verify", verify); @@ -597,6 +607,36 @@ doRemoteOpen(virConnectPtr conn, break; + case trans_libssh: + if (!sockname) { + if (flags & VIR_DRV_OPEN_REMOTE_RO) + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET_RO); + else + sockname = strdup(LIBVIRTD_PRIV_UNIX_SOCKET); + + if (sockname == NULL) + goto no_memory; + } + + VIR_DEBUG("Starting LibSSH2 session"); + + priv->client = virNetClientNewLibSSH(priv->hostname, + port, + username, + password, + keyfile, + knownHosts, + knownHostsVerify, + authtype, + netcat, + sockname, + auth); + if (!priv->client) + goto failed; + + priv->is_secure = 1; + break; + #ifndef WIN32 case trans_unix: if (!sockname) { @@ -777,6 +817,9 @@ doRemoteOpen(virConnectPtr conn, VIR_FREE(username); VIR_FREE(port); VIR_FREE(pkipath); + VIR_FREE(password); + VIR_FREE(knownHostsVerify); + VIR_FREE(knownHosts); return retcode; -- 1.7.8.6
participants (2)
-
Eric Blake
-
Peter Krempa