vendor/libgit2/src/transports/ssh.c in rugged-0.21.4 vs vendor/libgit2/src/transports/ssh.c in rugged-0.22.0b1
- old
+ new
@@ -11,10 +11,11 @@
#include "git2.h"
#include "buffer.h"
#include "netops.h"
#include "smart.h"
+#include "cred.h"
#ifdef GIT_SSH
#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
@@ -35,12 +36,16 @@
typedef struct {
git_smart_subtransport parent;
transport_smart *owner;
ssh_stream *current_stream;
git_cred *cred;
+ char *cmd_uploadpack;
+ char *cmd_receivepack;
} ssh_subtransport;
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
+
static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
{
char *ssherr;
libssh2_session_last_error(session, &ssherr, NULL, 0);
@@ -355,19 +360,59 @@
default:
rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
}
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+ if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
+ return GIT_EAUTH;
+
if (rc != LIBSSH2_ERROR_NONE) {
if (!giterr_last())
ssh_error(session, "Failed to authenticate SSH session");
return -1;
}
return 0;
}
+static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods)
+{
+ int error, no_callback = 0;
+ git_cred *cred = NULL;
+
+ if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH)
+ no_callback = 1;
+ else if (error < 0)
+ return error;
+ else if (!cred) {
+ giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
+ return -1;
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_SSH, "authentication required but no callback set");
+ return -1;
+ }
+
+ if (!(cred->credtype & auth_methods)) {
+ cred->free(cred);
+ giterr_set(GITERR_SSH, "callback returned unsupported credentials type");
+ return -1;
+ }
+
+ *out = cred;
+
+ return 0;
+}
+
static int _git_ssh_session_create(
LIBSSH2_SESSION** session,
gitno_socket socket)
{
int rc = 0;
@@ -404,118 +449,167 @@
const char *cmd,
git_smart_subtransport_stream **stream)
{
char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
const char *default_port="22";
- int no_callback = 0;
+ int auth_methods, error = 0;
ssh_stream *s;
+ git_cred *cred = NULL;
LIBSSH2_SESSION* session=NULL;
LIBSSH2_CHANNEL* channel=NULL;
+ t->current_stream = NULL;
+
*stream = NULL;
if (ssh_stream_alloc(t, url, cmd, stream) < 0)
return -1;
s = (ssh_stream *)*stream;
+ s->session = NULL;
+ s->channel = NULL;
if (!git__prefixcmp(url, prefix_ssh)) {
- if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0)
- goto on_error;
+ if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
+ goto done;
} else {
- if (git_ssh_extract_url_parts(&host, &user, url) < 0)
- goto on_error;
+ if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
+ goto done;
port = git__strdup(default_port);
GITERR_CHECK_ALLOC(port);
}
- if (gitno_connect(&s->socket, host, port, 0) < 0)
- goto on_error;
+ if ((error = gitno_connect(&s->socket, host, port, 0)) < 0)
+ goto done;
- if (user && pass) {
- if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
- goto on_error;
- } else if (!t->owner->cred_acquire_cb) {
- no_callback = 1;
- } else {
- int error;
- error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
- GIT_CREDTYPE_USERPASS_PLAINTEXT |
- GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
- GIT_CREDTYPE_SSH_INTERACTIVE,
- t->owner->cred_acquire_payload);
+ if ((error = _git_ssh_session_create(&session, s->socket)) < 0)
+ goto done;
- if (error == GIT_PASSTHROUGH)
- no_callback = 1;
- else if (error < 0)
- goto on_error;
- else if (!t->cred) {
- giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
- goto on_error;
+ if (t->owner->certificate_check_cb != NULL) {
+ git_cert_hostkey cert = { 0 }, *cert_ptr;
+ const char *key;
+
+ cert.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_SHA1;
+ memcpy(&cert.hash_sha1, key, 20);
}
+
+ key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ if (key != NULL) {
+ cert.type |= GIT_CERT_SSH_MD5;
+ memcpy(&cert.hash_md5, key, 16);
+ }
+
+ if (cert.type == 0) {
+ giterr_set(GITERR_SSH, "unable to get the host key");
+ error = -1;
+ goto done;
+ }
+
+ /* We don't currently trust any hostkeys */
+ giterr_clear();
+
+ cert_ptr = &cert;
+
+ error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload);
+ if (error < 0) {
+ if (!giterr_last())
+ giterr_set(GITERR_NET, "user cancelled hostkey check");
+
+ goto done;
+ }
}
- if (no_callback) {
- giterr_set(GITERR_SSH, "authentication required but no callback set");
- goto on_error;
+ /* we need the username to ask for auth methods */
+ if (!user) {
+ if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
+ goto done;
+
+ user = git__strdup(((git_cred_username *) cred)->username);
+ cred->free(cred);
+ cred = NULL;
+ if (!user)
+ goto done;
+ } else if (user && pass) {
+ if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0)
+ goto done;
}
- assert(t->cred);
+ if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
+ goto done;
- if (_git_ssh_session_create(&session, s->socket) < 0)
- goto on_error;
+ error = GIT_EAUTH;
+ /* if we already have something to try */
+ if (cred && auth_methods & cred->credtype)
+ error = _git_ssh_authenticate_session(session, cred);
- if (_git_ssh_authenticate_session(session, t->cred) < 0)
- goto on_error;
+ while (error == GIT_EAUTH) {
+ if (cred) {
+ cred->free(cred);
+ cred = NULL;
+ }
+ if ((error = request_creds(&cred, t, user, auth_methods)) < 0)
+ goto done;
+
+ if (strcmp(user, git_cred__username(cred))) {
+ giterr_set(GITERR_SSH, "username does not match previous request");
+ error = -1;
+ goto done;
+ }
+
+ error = _git_ssh_authenticate_session(session, cred);
+ }
+
+ if (error < 0)
+ goto done;
+
channel = libssh2_channel_open_session(session);
if (!channel) {
+ error = -1;
ssh_error(session, "Failed to open SSH channel");
- goto on_error;
+ goto done;
}
libssh2_channel_set_blocking(channel, 1);
s->session = session;
s->channel = channel;
t->current_stream = s;
- git__free(host);
- git__free(port);
- git__free(path);
- git__free(user);
- git__free(pass);
- return 0;
+done:
+ if (error < 0) {
+ if (*stream)
+ ssh_stream_free(*stream);
-on_error:
- s->session = NULL;
- s->channel = NULL;
- t->current_stream = NULL;
+ if (session)
+ libssh2_session_free(session);
+ }
- if (*stream)
- ssh_stream_free(*stream);
+ if (cred)
+ cred->free(cred);
git__free(host);
git__free(port);
+ git__free(path);
git__free(user);
git__free(pass);
- if (session)
- libssh2_session_free(session);
-
- return -1;
+ return error;
}
static int ssh_uploadpack_ls(
ssh_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
{
- if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
- return -1;
+ const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
- return 0;
+ return _git_ssh_setup_conn(t, url, cmd, stream);
}
static int ssh_uploadpack(
ssh_subtransport *t,
const char *url,
@@ -535,14 +629,14 @@
static int ssh_receivepack_ls(
ssh_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
{
- if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
- return -1;
+ const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
- return 0;
+
+ return _git_ssh_setup_conn(t, url, cmd, stream);
}
static int ssh_receivepack(
ssh_subtransport *t,
const char *url,
@@ -600,12 +694,61 @@
{
ssh_subtransport *t = (ssh_subtransport *) subtransport;
assert(!t->current_stream);
+ git__free(t->cmd_uploadpack);
+ git__free(t->cmd_receivepack);
git__free(t);
}
+
+#define SSH_AUTH_PUBLICKEY "publickey"
+#define SSH_AUTH_PASSWORD "password"
+#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
+
+static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
+{
+ const char *list, *ptr;
+
+ *out = 0;
+
+ list = libssh2_userauth_list(session, username, strlen(username));
+
+ /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
+ if (list == NULL && !libssh2_userauth_authenticated(session))
+ return -1;
+
+ ptr = list;
+ while (ptr) {
+ if (*ptr == ',')
+ ptr++;
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
+ *out |= GIT_CREDTYPE_SSH_KEY;
+ *out |= GIT_CREDTYPE_SSH_CUSTOM;
+ ptr += strlen(SSH_AUTH_PUBLICKEY);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
+ *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ ptr += strlen(SSH_AUTH_PASSWORD);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
+ *out |= GIT_CREDTYPE_SSH_INTERACTIVE;
+ ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
+ continue;
+ }
+
+ /* Skipt it if we don't know it */
+ ptr = strchr(ptr, ',');
+ }
+
+ return 0;
+}
#endif
int git_smart_subtransport_ssh(
git_smart_subtransport **out, git_transport *owner)
{
@@ -624,9 +767,52 @@
*out = (git_smart_subtransport *) t;
return 0;
#else
GIT_UNUSED(owner);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
+
+int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
+{
+#ifdef GIT_SSH
+ git_strarray *paths = (git_strarray *) payload;
+ git_transport *transport;
+ transport_smart *smart;
+ ssh_subtransport *t;
+ int error;
+ git_smart_subtransport_definition ssh_definition = {
+ git_smart_subtransport_ssh,
+ 0, /* no RPC */
+ };
+
+ if (paths->count != 2) {
+ giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
+ return error;
+
+ smart = (transport_smart *) transport;
+ t = (ssh_subtransport *) smart->wrapped;
+
+ t->cmd_uploadpack = git__strdup(paths->strings[0]);
+ GITERR_CHECK_ALLOC(t->cmd_uploadpack);
+ t->cmd_receivepack = git__strdup(paths->strings[1]);
+ GITERR_CHECK_ALLOC(t->cmd_receivepack);
+
+ *out = transport;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+ GIT_UNUSED(payload);
assert(out);
*out = NULL;
giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");