diff --git a/src/transports/ssh.c b/src/transports/ssh.c index b403727c9..8ee080be2 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -37,6 +37,8 @@ typedef struct { git_cred *cred; } ssh_subtransport; +static int list_auth_methods(int *out, const char *host, const char *port); + static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) { char *ssherr; @@ -387,7 +389,7 @@ static int _git_ssh_setup_conn( { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; - int no_callback = 0; + int no_callback = 0, auth_methods; ssh_stream *s; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; @@ -408,6 +410,12 @@ static int _git_ssh_setup_conn( GITERR_CHECK_ALLOC(port); } + if (list_auth_methods(&auth_methods, host, port) < 0) { + auth_methods = GIT_CREDTYPE_USERPASS_PLAINTEXT | + GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM | + GIT_CREDTYPE_SSH_INTERACTIVE; + } + if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; @@ -418,11 +426,8 @@ static int _git_ssh_setup_conn( 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); + error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user, auth_methods, + t->owner->cred_acquire_payload); if (error == GIT_PASSTHROUGH) no_callback = 1; @@ -585,6 +590,72 @@ static void _ssh_free(git_smart_subtransport *subtransport) 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, const char *host, const char *port) +{ + gitno_socket s; + LIBSSH2_SESSION *session = NULL; + const char *list, *ptr; + + *out = 0; + + if (gitno_connect(&s, host, port, 0) < 0) + return -1; + + if (_git_ssh_session_create(&session, s) < 0) + goto on_error; + + /* the username is dummy, we're throwing away the connection anyway */ + list = libssh2_userauth_list(session, "git", strlen("git")); + + /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ + if (list == NULL) + goto out; + + 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, ','); + } + +out: + libssh2_session_free(session); + gitno_close(&s); + + return 0; + +on_error: + libssh2_session_free(session); + gitno_close(&s); + + return -1; +} #endif int git_smart_subtransport_ssh( diff --git a/tests/online/clone.c b/tests/online/clone.c index 8a2a64f95..8660d31b3 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -13,6 +13,8 @@ #define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git" #define ASSEMBLA_REPO_URL "https://libgit2:_Libgit2@git.assembla.com/libgit2-test-repos.git" +#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository" + static git_repository *g_repo; static git_clone_options g_options; @@ -313,7 +315,20 @@ void test_online_clone__can_cancel(void) } +static int check_ssh_auth_methods(git_cred **cred, const char *url, const char *username_from_url, + unsigned int allowed_types, void *data) +{ + GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data); + cl_assert_equal_i(GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM, allowed_types); + return GIT_EUSER; +} +void test_online_clone__ssh_auth_methods(void) +{ + g_options.remote_callbacks.credentials = check_ssh_auth_methods; + cl_git_fail_with(GIT_EUSER, + git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); +}