ssh: detect authentication methods Before calling the credentials callback, ask the sever which authentication methods it supports and report that to the user, instead of simply reporting everything that the transport supports. In case of an error, we do fall back to listing all of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index b403727..8ee080b 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 8a2a64f..8660d31 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));
+}