[libvirt] [PATCHv2 1/4] libssh2_transport: add main libssh2 transport implementation
Daniel P. Berrange
berrange at redhat.com
Wed Aug 15 13:27:35 UTC 2012
On Sat, Aug 11, 2012 at 11:20:59PM +0200, Peter Krempa wrote:
> diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
> index 913fc5d..9c751b6 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
> @@ -279,6 +280,7 @@ typedef enum {
> VIR_ERR_BLOCK_COPY_ACTIVE = 83, /* action prevented by block copy job */
> VIR_ERR_OPERATION_UNSUPPORTED = 84, /* The requested operation is not
> supported */
> + VIR_ERR_LIBSSH2_ERROR = 85, /* error in libssh2 transport driver */
Using '_ERROR' as a suffix is redundant here - just call it VIR_ERR_LIBSSH2
> diff --git a/src/rpc/virnetlibssh2session.c b/src/rpc/virnetlibssh2session.c
> +struct _virNetLibSSH2AuthMethod {
> + virNetLibSSH2AuthMethods method;
> + const char *username;
> + const char *password;
> + const char *filename;
The strings here are all dynamically allocated and freed,
so these shouldn't be marked 'const'.
> +struct _virNetLibSSH2Session {
> + virObject object;
> + virNetLibSSH2SessionState state;
> + virMutex lock;
> +
> + /* libssh2 internal stuff */
> + LIBSSH2_SESSION *session;
> + LIBSSH2_CHANNEL *channel;
> + LIBSSH2_KNOWNHOSTS *knownHosts;
> + LIBSSH2_AGENT *agent;
> +
> + /* for host key checking */
> + virNetLibSSH2HostkeyVerify hostKeyVerify;
> + const char *knownHostsFile;
> + const char *hostname;
Same as above - remove the const
> + int port;
> +
> + /* authentication stuff */
> + virConnectAuthPtr cred;
> + virNetLibSSH2AuthCallbackError authCbErr;
> + size_t nauths;
> + virNetLibSSH2AuthMethodPtr *auths;
> +
> + /* channel stuff */
> + const char *channelCommand;
Remove the const
> + int channelCommandReturnValue;
> +
> + /* read cache */
> + char rbuf[VIR_NET_LIBSSH2_BUFFER_SIZE];
> + size_t bufUsed;
> + size_t bufStart;
> +};
> +
> +static virClassPtr virNetLibSSH2SessionClass;
> +static int
> +virNetLibSSH2SessionOnceInit(void)
> +{
> + if (!(virNetLibSSH2SessionClass = virClassNew("virNetLibSSH2Session",
> + sizeof(virNetLibSSH2Session),
> + virNetLibSSH2SessionDispose)))
> + return -1;
> +
> + return 0;
> +}
> +static int
> +virNetLibSSH2AuthenticatePrivkey(virNetLibSSH2SessionPtr sess,
> + virNetLibSSH2AuthMethodPtr 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_LIBSSH2_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_LIBSSH2_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_LIBSSH2_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
> +virNetLibSSH2AuthenticatePassword(virNetLibSSH2SessionPtr sess,
> + virNetLibSSH2AuthMethodPtr 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
> +virNetLibSSH2AuthenticateKeyboardInteractive(virNetLibSSH2SessionPtr sess,
> + virNetLibSSH2AuthMethodPtr priv)
> +{
> + char *errmsg;
> + int ret;
> +
> + if (!sess->cred || !sess->cred->cb) {
> + virReportError(VIR_ERR_LIBSSH2_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) {
If this is supposed to be infinit as the comment suggests, then
the normal paradigm is
while (1) {
> + ret = libssh2_userauth_keyboard_interactive(sess->session,
> + priv->username,
> + virNetLibSSH2KbIntCb);
> +
> + /* check for errors while calling the callback */
> + switch (sess->authCbErr) {
> + case VIR_NET_LIBSSH2_AUTHCB_NO_METHOD:
> + virReportError(VIR_ERR_LIBSSH2_ERROR, "%s",
> + _("no suitable method to retrieve "
> + "authentication cretentials"));
> + return -1;
> + case VIR_NET_LIBSSH2_AUTHCB_OOM:
> + virReportOOMError();
> + return -1;
> + case VIR_NET_LIBSSH2_AUTHCB_RETR_ERR:
> + virReportError(VIR_ERR_LIBSSH2_ERROR, "%s",
> + _("failed to retrieve credentials"));
> + return -1;
> + case VIR_NET_LIBSSH2_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;
> +}
> +int
> +virNetLibSSH2SessionAuthAddKeyboardAuth(virNetLibSSH2SessionPtr sess,
> + const char *username,
> + int tries)
> +{
> + virNetLibSSH2AuthMethodPtr auth;
> + char *user = NULL;
> +
> + if (!username) {
> + virReportError(VIR_ERR_LIBSSH2_ERROR, "%s",
> + _("Username must be provided "
> + "for ssh agent authentication"));
> + return -1;
> + }
> +
> + virMutexLock(&sess->lock);
> +
> + if (!(user = strdup(username)))
> + goto no_memory;
> +
> +
> + if (!(auth = virNetLibSSH2SessionAuthMethodNew(sess))) {
> + virReportOOMError();
> + virMutexUnlock(&sess->lock);
> + return -1;
'goto no_memory' otherwise you leak 'user'
> + }
> +
> + auth->username = user;
> + auth->tries = tries;
> + auth->method = VIR_NET_LIBSSH2_AUTH_KEYBOARD_INTERACTIVE;
> +
> + virMutexUnlock(&sess->lock);
> + return 0;
> +
> +no_memory:
> + VIR_FREE(user);
> + virReportOOMError();
> + virMutexUnlock(&sess->lock);
> + return -1;
> +
> +}
> +
> +int
> +virNetLibSSH2SessionSetHostKeyVerification(virNetLibSSH2SessionPtr sess,
> + const char *hostname,
> + int port,
> + const char *hostsfile,
> + bool readonly,
> + virNetLibSSH2HostkeyVerify 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_LIBSSH2_ERROR,
> + _("unable to load knownhosts file '%s': %s"),
> + hostsfile, errmsg);
> + }
A return -1 or goto error missing here ?
> +
> + /* 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;
> +}
> +
> +/* do a read from a ssh channel, used instead of normal read on socket */
> +ssize_t
> +virNetLibSSH2ChannelRead(virNetLibSSH2SessionPtr sess,
> + char *buf,
> + size_t len)
> +{
> + ssize_t ret = -1;
> + ssize_t read_n = 0;
> +
> + virMutexLock(&sess->lock);
> +
> + if (sess->state != VIR_NET_LIBSSH2_STATE_HANDSHAKE_COMPLETE) {
> + if (sess->state == VIR_NET_LIBSSH2_STATE_ERROR_REMOTE)
> + virReportError(VIR_ERR_LIBSSH2_ERROR,
> + _("Remote program terminated "
> + "with non-zero code: %d"),
> + sess->channelCommandReturnValue);
> + else
> + virReportError(VIR_ERR_LIBSSH2_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);
Can you add whitespace around the '?' and ':'
> +
> + 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_LIBSSH2_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_LIBSSH2_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_LIBSSH2_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_LIBSSH2_STATE_ERROR_REMOTE;
> + virMutexUnlock(&sess->lock);
> + return -1;
> + }
> +
> + sess->state = VIR_NET_LIBSSH2_STATE_CLOSED;
> + virMutexUnlock(&sess->lock);
> + return -1;
> + }
> +
> +success:
> + virMutexUnlock(&sess->lock);
> + return read_n;
> +
> +error:
> + sess->state = VIR_NET_LIBSSH2_STATE_ERROR;
> + virMutexUnlock(&sess->lock);
> + return ret;
> +}
> +
> diff --git a/src/rpc/virnetlibssh2session.h b/src/rpc/virnetlibssh2session.h
> new file mode 100644
> index 0000000..88d4307
> --- /dev/null
> +++ b/src/rpc/virnetlibssh2session.h
> +# include "internal.h"
> +
> +typedef struct _virNetLibSSH2Session virNetLibSSH2Session;
> +typedef virNetLibSSH2Session *virNetLibSSH2SessionPtr;
> +
> +virNetLibSSH2SessionPtr virNetLibSSH2SessionNew(void);
> +void virNetLibSSH2SessionFree(virNetLibSSH2SessionPtr sess);
> +
> +typedef enum {
> + VIR_NET_LIBSSH2_HOSTKEY_VERIFY_NORMAL,
> + VIR_NET_LIBSSH2_HOSTKEY_VERIFY_AUTO_ADD,
> + VIR_NET_LIBSSH2_HOSTKEY_VERIFY_IGNORE
> +} virNetLibSSH2HostkeyVerify;
> +
> +int virNetLibSSH2SessionSetChannelCommand(virNetLibSSH2SessionPtr sess,
> + const char *command);
> +
> +void virNetLibSSH2SessionAuthReset(virNetLibSSH2SessionPtr sess);
> +
> +int virNetLibSSH2SessionAuthSetCallback(virNetLibSSH2SessionPtr sess,
> + virConnectAuthPtr auth);
> +
> +int virNetLibSSH2SessionAuthAddPasswordAuth(virNetLibSSH2SessionPtr sess,
> + const char *username,
> + const char *password);
> +
> +int virNetLibSSH2SessionAuthAddAgentAuth(virNetLibSSH2SessionPtr sess,
> + const char *username);
> +
> +int virNetLibSSH2SessionAuthAddPrivKeyAuth(virNetLibSSH2SessionPtr sess,
> + const char *username,
> + const char *keyfile,
> + const char *password);
> +
> +int virNetLibSSH2SessionAuthAddKeyboardAuth(virNetLibSSH2SessionPtr sess,
> + const char *username,
> + int tries);
> +
> +int virNetLibSSH2SessionSetHostKeyVerification(virNetLibSSH2SessionPtr sess,
> + const char *hostname,
> + int port,
> + const char *hostsfile,
> + bool readonly,
> + virNetLibSSH2HostkeyVerify opt);
> +
> +int virNetLibSSH2SessionConnect(virNetLibSSH2SessionPtr sess,
> + int sock);
> +
> +ssize_t virNetLibSSH2ChannelRead(virNetLibSSH2SessionPtr sess,
> + char *buf,
> + size_t len);
> +
> +ssize_t virNetLibSSH2ChannelWrite(virNetLibSSH2SessionPtr sess,
> + const char *buf,
> + size_t len);
> +
> +bool virNetLibSSH2SessionHasCachedData(virNetLibSSH2SessionPtr sess);
> +
> +#endif /* ___VIR_NET_LIBSSH2_SESSION_H_ */
I think I'd be inclined to change the filename and API names throughout
this patch to just use 'ssh' instead of 'libssh2'. eg
virNetSSHSessionHasCachedData
and virnetsshsession.{c,h}
For comparison see the SASL or TLS code which uses a naming that is
independant of the implementation (Cyrus-SASL / GNU-TLS).
ACK if you rename the APIs and fix the couple of minor issues mentioned
inline
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
More information about the libvir-list
mailing list