]>
Commit | Line | Data |
---|---|---|
297758dc BM |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
ae241ae1 JG |
8 | #ifdef GIT_SSH |
9 | #include <libssh2.h> | |
10 | #endif | |
11 | ||
297758dc BM |
12 | #include "git2.h" |
13 | #include "buffer.h" | |
14 | #include "netops.h" | |
f7158cd7 | 15 | #include "smart.h" |
ccb85c8f | 16 | #include "cred.h" |
297758dc | 17 | |
a3c062db RB |
18 | #ifdef GIT_SSH |
19 | ||
297758dc BM |
20 | #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) |
21 | ||
d04c3840 | 22 | static const char prefix_ssh[] = "ssh://"; |
297758dc BM |
23 | static const char cmd_uploadpack[] = "git-upload-pack"; |
24 | static const char cmd_receivepack[] = "git-receive-pack"; | |
25 | ||
26 | typedef struct { | |
27 | git_smart_subtransport_stream parent; | |
28 | gitno_socket socket; | |
f7158cd7 BM |
29 | LIBSSH2_SESSION *session; |
30 | LIBSSH2_CHANNEL *channel; | |
297758dc BM |
31 | const char *cmd; |
32 | char *url; | |
33 | unsigned sent_command : 1; | |
34 | } ssh_stream; | |
35 | ||
36 | typedef struct { | |
37 | git_smart_subtransport parent; | |
f7158cd7 | 38 | transport_smart *owner; |
8ae55d94 | 39 | ssh_stream *current_stream; |
f7158cd7 | 40 | git_cred *cred; |
d4256ed5 CMN |
41 | char *cmd_uploadpack; |
42 | char *cmd_receivepack; | |
297758dc BM |
43 | } ssh_subtransport; |
44 | ||
ccb85c8f | 45 | static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); |
22618906 | 46 | |
b622aabe ES |
47 | static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) |
48 | { | |
49 | char *ssherr; | |
50 | libssh2_session_last_error(session, &ssherr, NULL, 0); | |
51 | ||
52 | giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr); | |
53 | } | |
54 | ||
297758dc BM |
55 | /* |
56 | * Create a git protocol request. | |
57 | * | |
58ba0a4e | 58 | * For example: git-upload-pack '/libgit2/libgit2' |
297758dc | 59 | */ |
7261d983 | 60 | static int gen_proto(git_buf *request, const char *cmd, const char *url) |
297758dc | 61 | { |
7261d983 | 62 | char *repo; |
300f4412 | 63 | int len; |
a3c062db | 64 | |
7261d983 BM |
65 | if (!git__prefixcmp(url, prefix_ssh)) { |
66 | url = url + strlen(prefix_ssh); | |
67 | repo = strchr(url, '/'); | |
68 | } else { | |
69 | repo = strchr(url, ':'); | |
b345026b | 70 | if (repo) repo++; |
7261d983 | 71 | } |
a3c062db | 72 | |
7261d983 | 73 | if (!repo) { |
c2de6b1a | 74 | giterr_set(GITERR_NET, "Malformed git protocol URL"); |
7261d983 BM |
75 | return -1; |
76 | } | |
a3c062db | 77 | |
300f4412 | 78 | len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; |
a3c062db | 79 | |
297758dc | 80 | git_buf_grow(request, len); |
f7158cd7 | 81 | git_buf_printf(request, "%s '%s'", cmd, repo); |
297758dc | 82 | git_buf_putc(request, '\0'); |
a3c062db | 83 | |
297758dc BM |
84 | if (git_buf_oom(request)) |
85 | return -1; | |
a3c062db | 86 | |
297758dc BM |
87 | return 0; |
88 | } | |
89 | ||
8ae55d94 | 90 | static int send_command(ssh_stream *s) |
297758dc BM |
91 | { |
92 | int error; | |
93 | git_buf request = GIT_BUF_INIT; | |
a3c062db | 94 | |
7261d983 | 95 | error = gen_proto(&request, s->cmd, s->url); |
297758dc BM |
96 | if (error < 0) |
97 | goto cleanup; | |
a3c062db | 98 | |
c2de6b1a | 99 | error = libssh2_channel_exec(s->channel, request.ptr); |
b622aabe ES |
100 | if (error < LIBSSH2_ERROR_NONE) { |
101 | ssh_error(s->session, "SSH could not execute request"); | |
f7158cd7 | 102 | goto cleanup; |
c2de6b1a | 103 | } |
a3c062db | 104 | |
f7158cd7 | 105 | s->sent_command = 1; |
a3c062db | 106 | |
297758dc BM |
107 | cleanup: |
108 | git_buf_free(&request); | |
109 | return error; | |
110 | } | |
111 | ||
8ae55d94 BM |
112 | static int ssh_stream_read( |
113 | git_smart_subtransport_stream *stream, | |
114 | char *buffer, | |
115 | size_t buf_size, | |
116 | size_t *bytes_read) | |
297758dc | 117 | { |
c2de6b1a | 118 | int rc; |
8ae55d94 | 119 | ssh_stream *s = (ssh_stream *)stream; |
a3c062db | 120 | |
297758dc | 121 | *bytes_read = 0; |
a3c062db | 122 | |
297758dc BM |
123 | if (!s->sent_command && send_command(s) < 0) |
124 | return -1; | |
a3c062db | 125 | |
b622aabe ES |
126 | if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { |
127 | ssh_error(s->session, "SSH could not read data");; | |
297758dc | 128 | return -1; |
c2de6b1a | 129 | } |
a3c062db | 130 | |
f7158cd7 | 131 | *bytes_read = rc; |
a3c062db | 132 | |
297758dc BM |
133 | return 0; |
134 | } | |
135 | ||
8ae55d94 BM |
136 | static int ssh_stream_write( |
137 | git_smart_subtransport_stream *stream, | |
138 | const char *buffer, | |
139 | size_t len) | |
297758dc | 140 | { |
8ae55d94 | 141 | ssh_stream *s = (ssh_stream *)stream; |
0963716b CMN |
142 | size_t off = 0; |
143 | ssize_t ret = 0; | |
a3c062db | 144 | |
297758dc BM |
145 | if (!s->sent_command && send_command(s) < 0) |
146 | return -1; | |
a3c062db | 147 | |
0963716b CMN |
148 | do { |
149 | ret = libssh2_channel_write(s->channel, buffer + off, len - off); | |
150 | if (ret < 0) | |
151 | break; | |
152 | ||
153 | off += ret; | |
154 | ||
155 | } while (off < len); | |
156 | ||
157 | if (ret < 0) { | |
b622aabe | 158 | ssh_error(s->session, "SSH could not write data"); |
f7158cd7 BM |
159 | return -1; |
160 | } | |
a3c062db | 161 | |
c2de6b1a | 162 | return 0; |
297758dc BM |
163 | } |
164 | ||
8ae55d94 | 165 | static void ssh_stream_free(git_smart_subtransport_stream *stream) |
297758dc | 166 | { |
8ae55d94 | 167 | ssh_stream *s = (ssh_stream *)stream; |
297758dc BM |
168 | ssh_subtransport *t = OWNING_SUBTRANSPORT(s); |
169 | int ret; | |
a3c062db | 170 | |
297758dc | 171 | GIT_UNUSED(ret); |
a3c062db | 172 | |
297758dc | 173 | t->current_stream = NULL; |
a3c062db | 174 | |
22595b84 BM |
175 | if (s->channel) { |
176 | libssh2_channel_close(s->channel); | |
68bc49a1 RB |
177 | libssh2_channel_free(s->channel); |
178 | s->channel = NULL; | |
22595b84 | 179 | } |
a3c062db | 180 | |
22595b84 | 181 | if (s->session) { |
c2de6b1a RB |
182 | libssh2_session_free(s->session); |
183 | s->session = NULL; | |
22595b84 | 184 | } |
a3c062db | 185 | |
297758dc | 186 | if (s->socket.socket) { |
c2de6b1a RB |
187 | (void)gitno_close(&s->socket); |
188 | /* can't do anything here with error return value */ | |
297758dc | 189 | } |
a3c062db | 190 | |
297758dc BM |
191 | git__free(s->url); |
192 | git__free(s); | |
193 | } | |
194 | ||
8ae55d94 BM |
195 | static int ssh_stream_alloc( |
196 | ssh_subtransport *t, | |
197 | const char *url, | |
198 | const char *cmd, | |
199 | git_smart_subtransport_stream **stream) | |
297758dc | 200 | { |
8ae55d94 | 201 | ssh_stream *s; |
a3c062db | 202 | |
c2de6b1a | 203 | assert(stream); |
a3c062db | 204 | |
8ae55d94 | 205 | s = git__calloc(sizeof(ssh_stream), 1); |
297758dc | 206 | GITERR_CHECK_ALLOC(s); |
a3c062db | 207 | |
297758dc | 208 | s->parent.subtransport = &t->parent; |
8ae55d94 BM |
209 | s->parent.read = ssh_stream_read; |
210 | s->parent.write = ssh_stream_write; | |
211 | s->parent.free = ssh_stream_free; | |
a3c062db | 212 | |
297758dc | 213 | s->cmd = cmd; |
a3c062db | 214 | |
c2de6b1a | 215 | s->url = git__strdup(url); |
297758dc BM |
216 | if (!s->url) { |
217 | git__free(s); | |
218 | return -1; | |
219 | } | |
a3c062db | 220 | |
297758dc BM |
221 | *stream = &s->parent; |
222 | return 0; | |
223 | } | |
224 | ||
7261d983 | 225 | static int git_ssh_extract_url_parts( |
d04c3840 BM |
226 | char **host, |
227 | char **username, | |
d04c3840 BM |
228 | const char *url) |
229 | { | |
230 | char *colon, *at; | |
231 | const char *start; | |
a3c062db | 232 | |
68bc49a1 | 233 | colon = strchr(url, ':'); |
a3c062db | 234 | |
a3c062db | 235 | |
7261d983 | 236 | at = strchr(url, '@'); |
d04c3840 | 237 | if (at) { |
eec4dcc3 | 238 | start = at + 1; |
d04c3840 | 239 | *username = git__substrdup(url, at - url); |
7affc2f7 | 240 | GITERR_CHECK_ALLOC(*username); |
d04c3840 | 241 | } else { |
c87bf86c | 242 | start = url; |
7affc2f7 | 243 | *username = NULL; |
d04c3840 | 244 | } |
a3c062db | 245 | |
048f837b BS |
246 | if (colon == NULL || (colon < start)) { |
247 | giterr_set(GITERR_NET, "Malformed URL"); | |
248 | return -1; | |
249 | } | |
250 | ||
d04c3840 | 251 | *host = git__substrdup(start, colon - start); |
c2de6b1a | 252 | GITERR_CHECK_ALLOC(*host); |
a3c062db | 253 | |
d04c3840 BM |
254 | return 0; |
255 | } | |
256 | ||
ee7040fd AG |
257 | static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) { |
258 | int rc = LIBSSH2_ERROR_NONE; | |
259 | ||
260 | struct libssh2_agent_publickey *curr, *prev = NULL; | |
261 | ||
262 | LIBSSH2_AGENT *agent = libssh2_agent_init(session); | |
263 | ||
264 | if (agent == NULL) | |
265 | return -1; | |
266 | ||
267 | rc = libssh2_agent_connect(agent); | |
268 | ||
269 | if (rc != LIBSSH2_ERROR_NONE) | |
270 | goto shutdown; | |
271 | ||
272 | rc = libssh2_agent_list_identities(agent); | |
273 | ||
274 | if (rc != LIBSSH2_ERROR_NONE) | |
275 | goto shutdown; | |
276 | ||
277 | while (1) { | |
278 | rc = libssh2_agent_get_identity(agent, &curr, prev); | |
279 | ||
280 | if (rc < 0) | |
281 | goto shutdown; | |
282 | ||
283 | if (rc == 1) | |
284 | goto shutdown; | |
285 | ||
286 | rc = libssh2_agent_userauth(agent, c->username, curr); | |
287 | ||
288 | if (rc == 0) | |
289 | break; | |
290 | ||
291 | prev = curr; | |
292 | } | |
293 | ||
294 | shutdown: | |
295 | libssh2_agent_disconnect(agent); | |
296 | libssh2_agent_free(agent); | |
297 | ||
298 | return rc; | |
299 | } | |
300 | ||
c0cef9e0 BM |
301 | static int _git_ssh_authenticate_session( |
302 | LIBSSH2_SESSION* session, | |
68bc49a1 | 303 | git_cred* cred) |
c0cef9e0 BM |
304 | { |
305 | int rc; | |
c2de6b1a | 306 | |
c0cef9e0 BM |
307 | do { |
308 | switch (cred->credtype) { | |
c2de6b1a RB |
309 | case GIT_CREDTYPE_USERPASS_PLAINTEXT: { |
310 | git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; | |
bd270b70 | 311 | rc = libssh2_userauth_password(session, c->username, c->password); |
c2de6b1a RB |
312 | break; |
313 | } | |
70a8c78f CMN |
314 | case GIT_CREDTYPE_SSH_KEY: { |
315 | git_cred_ssh_key *c = (git_cred_ssh_key *)cred; | |
ee7040fd AG |
316 | |
317 | if (c->privatekey) | |
318 | rc = libssh2_userauth_publickey_fromfile( | |
319 | session, c->username, c->publickey, | |
320 | c->privatekey, c->passphrase); | |
321 | else | |
322 | rc = ssh_agent_auth(session, c); | |
323 | ||
c2de6b1a RB |
324 | break; |
325 | } | |
70a8c78f CMN |
326 | case GIT_CREDTYPE_SSH_CUSTOM: { |
327 | git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; | |
7affc2f7 | 328 | |
c2de6b1a | 329 | rc = libssh2_userauth_publickey( |
7affc2f7 | 330 | session, c->username, (const unsigned char *)c->publickey, |
8ec0a552 | 331 | c->publickey_len, c->sign_callback, &c->payload); |
c2de6b1a RB |
332 | break; |
333 | } | |
478408c0 JG |
334 | case GIT_CREDTYPE_SSH_INTERACTIVE: { |
335 | void **abstract = libssh2_session_abstract(session); | |
336 | git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred; | |
337 | ||
338 | /* ideally, we should be able to set this by calling | |
339 | * libssh2_session_init_ex() instead of libssh2_session_init(). | |
340 | * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() | |
341 | * allows you to pass the `abstract` as part of the call, whereas | |
342 | * libssh2_userauth_keyboard_interactive() does not! | |
343 | * | |
344 | * The only way to set the `abstract` pointer is by calling | |
345 | * libssh2_session_abstract(), which will replace the existing | |
346 | * pointer as is done below. This is safe for now (at time of writing), | |
347 | * but may not be valid in future. | |
348 | */ | |
349 | *abstract = c->payload; | |
350 | ||
351 | rc = libssh2_userauth_keyboard_interactive( | |
352 | session, c->username, c->prompt_callback); | |
353 | break; | |
354 | } | |
c2de6b1a RB |
355 | default: |
356 | rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; | |
c0cef9e0 | 357 | } |
08bf80fa | 358 | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); |
a3c062db | 359 | |
d7f962f4 CMN |
360 | if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) |
361 | return GIT_EAUTH; | |
362 | ||
b622aabe ES |
363 | if (rc != LIBSSH2_ERROR_NONE) { |
364 | ssh_error(session, "Failed to authenticate SSH session"); | |
c2de6b1a RB |
365 | return -1; |
366 | } | |
367 | ||
368 | return 0; | |
c0cef9e0 BM |
369 | } |
370 | ||
d7f962f4 CMN |
371 | static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods) |
372 | { | |
373 | int error, no_callback = 0; | |
374 | git_cred *cred = NULL; | |
375 | ||
376 | if (!t->owner->cred_acquire_cb) { | |
377 | no_callback = 1; | |
378 | } else { | |
379 | error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods, | |
380 | t->owner->cred_acquire_payload); | |
381 | ||
382 | if (error == GIT_PASSTHROUGH) | |
383 | no_callback = 1; | |
384 | else if (error < 0) | |
385 | return error; | |
386 | else if (!cred) { | |
387 | giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials"); | |
388 | return -1; | |
389 | } | |
390 | } | |
391 | ||
392 | if (no_callback) { | |
393 | giterr_set(GITERR_SSH, "authentication required but no callback set"); | |
394 | return -1; | |
395 | } | |
396 | ||
ccb85c8f CMN |
397 | if (!(cred->credtype & auth_methods)) { |
398 | cred->free(cred); | |
399 | giterr_set(GITERR_SSH, "callback returned unsupported credentials type"); | |
400 | return -1; | |
401 | } | |
402 | ||
d7f962f4 CMN |
403 | *out = cred; |
404 | ||
405 | return 0; | |
406 | } | |
407 | ||
08bf80fa | 408 | static int _git_ssh_session_create( |
3eed595e | 409 | LIBSSH2_SESSION** session, |
68bc49a1 | 410 | gitno_socket socket) |
3eed595e | 411 | { |
c2de6b1a RB |
412 | int rc = 0; |
413 | LIBSSH2_SESSION* s; | |
a3c062db | 414 | |
c2de6b1a RB |
415 | assert(session); |
416 | ||
417 | s = libssh2_session_init(); | |
418 | if (!s) { | |
419 | giterr_set(GITERR_NET, "Failed to initialize SSH session"); | |
08bf80fa | 420 | return -1; |
c2de6b1a | 421 | } |
a3c062db | 422 | |
08bf80fa ES |
423 | do { |
424 | rc = libssh2_session_startup(s, socket.socket); | |
425 | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | |
a3c062db | 426 | |
b622aabe ES |
427 | if (rc != LIBSSH2_ERROR_NONE) { |
428 | ssh_error(s, "Failed to start SSH session"); | |
c2de6b1a | 429 | libssh2_session_free(s); |
c2de6b1a | 430 | return -1; |
68bc49a1 | 431 | } |
a3c062db | 432 | |
3eed595e | 433 | libssh2_session_set_blocking(s, 1); |
a3c062db | 434 | |
3eed595e | 435 | *session = s; |
a3c062db | 436 | |
3eed595e | 437 | return 0; |
3eed595e BM |
438 | } |
439 | ||
120b0122 | 440 | static int _git_ssh_setup_conn( |
8ae55d94 BM |
441 | ssh_subtransport *t, |
442 | const char *url, | |
120b0122 | 443 | const char *cmd, |
c2de6b1a | 444 | git_smart_subtransport_stream **stream) |
297758dc | 445 | { |
ac72051a | 446 | char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; |
3eed595e | 447 | const char *default_port="22"; |
d7f962f4 | 448 | int auth_methods, error = 0; |
8ae55d94 | 449 | ssh_stream *s; |
d7f962f4 | 450 | git_cred *cred = NULL; |
3eed595e BM |
451 | LIBSSH2_SESSION* session=NULL; |
452 | LIBSSH2_CHANNEL* channel=NULL; | |
a3c062db | 453 | |
297758dc | 454 | *stream = NULL; |
120b0122 | 455 | if (ssh_stream_alloc(t, url, cmd, stream) < 0) |
297758dc | 456 | return -1; |
a3c062db | 457 | |
8ae55d94 | 458 | s = (ssh_stream *)*stream; |
a3c062db | 459 | |
7261d983 | 460 | if (!git__prefixcmp(url, prefix_ssh)) { |
b529c5f9 | 461 | if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) |
c0cef9e0 | 462 | goto on_error; |
7261d983 | 463 | } else { |
b529c5f9 | 464 | if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) |
7261d983 BM |
465 | goto on_error; |
466 | port = git__strdup(default_port); | |
84ac625d | 467 | GITERR_CHECK_ALLOC(port); |
7261d983 | 468 | } |
a3c062db | 469 | |
ccb85c8f CMN |
470 | /* we need the username to ask for auth methods */ |
471 | if (!user) { | |
472 | if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) | |
473 | goto on_error; | |
a3c062db | 474 | |
ccb85c8f CMN |
475 | user = git__strdup(((git_cred_username *) cred)->username); |
476 | cred->free(cred); | |
477 | cred = NULL; | |
478 | if (!user) | |
479 | goto on_error; | |
480 | } else if (user && pass) { | |
d7f962f4 | 481 | if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) |
b54ed3ef | 482 | goto on_error; |
bc0a6198 CMN |
483 | } |
484 | ||
ccb85c8f CMN |
485 | if ((error = gitno_connect(&s->socket, host, port, 0)) < 0) |
486 | goto on_error; | |
487 | ||
d7f962f4 | 488 | if ((error = _git_ssh_session_create(&session, s->socket)) < 0) |
367c1903 | 489 | goto on_error; |
bc0a6198 | 490 | |
ccb85c8f CMN |
491 | if ((error = list_auth_methods(&auth_methods, session, user)) < 0) |
492 | goto on_error; | |
493 | ||
d7f962f4 CMN |
494 | error = GIT_EAUTH; |
495 | /* if we already have something to try */ | |
ccb85c8f | 496 | if (cred && auth_methods & cred->credtype) |
d7f962f4 | 497 | error = _git_ssh_authenticate_session(session, cred); |
a3c062db | 498 | |
d7f962f4 CMN |
499 | while (error == GIT_EAUTH) { |
500 | if (cred) { | |
501 | cred->free(cred); | |
502 | cred = NULL; | |
503 | } | |
504 | ||
505 | if ((error = request_creds(&cred, t, user, auth_methods)) < 0) | |
506 | goto on_error; | |
507 | ||
ccb85c8f CMN |
508 | if (strcmp(user, git_cred__username(cred))) { |
509 | giterr_set(GITERR_SSH, "username does not match previous request"); | |
510 | error = -1; | |
511 | goto on_error; | |
512 | } | |
513 | ||
d7f962f4 CMN |
514 | error = _git_ssh_authenticate_session(session, cred); |
515 | } | |
a3c062db | 516 | |
d7f962f4 | 517 | if (error < 0) |
3eed595e | 518 | goto on_error; |
a3c062db | 519 | |
ec1ce458 CMN |
520 | if (t->owner->certificate_check_cb != NULL) { |
521 | git_cert_hostkey cert; | |
522 | const char *key; | |
523 | int allow; | |
524 | size_t certlen; | |
525 | ||
526 | cert.type = LIBSSH2_HOSTKEY_HASH_SHA1; | |
527 | key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); | |
528 | if (key != NULL) { | |
529 | certlen = 20; | |
530 | memcpy(&cert.hash, key, certlen); | |
531 | } else { | |
532 | cert.type = LIBSSH2_HOSTKEY_HASH_MD5; | |
533 | key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); | |
534 | certlen = 16; | |
535 | if (key != NULL) | |
536 | memcpy(&cert.hash, key, certlen); | |
537 | } | |
9b940586 | 538 | |
ec1ce458 CMN |
539 | if (key == NULL) { |
540 | giterr_set(GITERR_SSH, "unable to get the host key"); | |
541 | return -1; | |
542 | } | |
9b940586 | 543 | |
17491f6e CMN |
544 | /* We don't currently trust any hostkeys */ |
545 | allow = t->owner->certificate_check_cb(GIT_CERT_HOSTKEY_LIBSSH2, &cert, certlen, 0, t->owner->message_cb_payload); | |
9b940586 CMN |
546 | if (allow < 0) { |
547 | error = allow; | |
548 | goto on_error; | |
549 | } | |
550 | ||
551 | if (!allow) { | |
552 | error = GIT_ECERTIFICATE; | |
553 | goto on_error; | |
554 | } | |
555 | } | |
556 | ||
3eed595e | 557 | channel = libssh2_channel_open_session(session); |
367c1903 | 558 | if (!channel) { |
b529c5f9 | 559 | error = -1; |
b622aabe | 560 | ssh_error(session, "Failed to open SSH channel"); |
9b940586 | 561 | error = -1; |
367c1903 ES |
562 | goto on_error; |
563 | } | |
a3c062db | 564 | |
d04c3840 | 565 | libssh2_channel_set_blocking(channel, 1); |
a3c062db | 566 | |
f7158cd7 BM |
567 | s->session = session; |
568 | s->channel = channel; | |
a3c062db | 569 | |
297758dc | 570 | t->current_stream = s; |
d7f962f4 CMN |
571 | if (cred) |
572 | cred->free(cred); | |
573 | ||
297758dc | 574 | git__free(host); |
3eed595e | 575 | git__free(port); |
ac72051a | 576 | git__free(path); |
22011b33 BM |
577 | git__free(user); |
578 | git__free(pass); | |
3eed595e | 579 | |
297758dc | 580 | return 0; |
a3c062db | 581 | |
297758dc | 582 | on_error: |
08bf80fa ES |
583 | s->session = NULL; |
584 | s->channel = NULL; | |
585 | t->current_stream = NULL; | |
586 | ||
297758dc | 587 | if (*stream) |
8ae55d94 | 588 | ssh_stream_free(*stream); |
a3c062db | 589 | |
d7f962f4 CMN |
590 | if (cred) |
591 | cred->free(cred); | |
592 | ||
297758dc | 593 | git__free(host); |
22011b33 BM |
594 | git__free(port); |
595 | git__free(user); | |
596 | git__free(pass); | |
597 | ||
7621519f | 598 | if (session) |
c2de6b1a | 599 | libssh2_session_free(session); |
3eed595e | 600 | |
b529c5f9 | 601 | return error; |
297758dc BM |
602 | } |
603 | ||
05f58131 | 604 | static int ssh_uploadpack_ls( |
120b0122 BM |
605 | ssh_subtransport *t, |
606 | const char *url, | |
607 | git_smart_subtransport_stream **stream) | |
608 | { | |
d4256ed5 CMN |
609 | const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; |
610 | ||
6a0d2b43 | 611 | return _git_ssh_setup_conn(t, url, cmd, stream); |
120b0122 BM |
612 | } |
613 | ||
05f58131 | 614 | static int ssh_uploadpack( |
120b0122 BM |
615 | ssh_subtransport *t, |
616 | const char *url, | |
617 | git_smart_subtransport_stream **stream) | |
618 | { | |
619 | GIT_UNUSED(url); | |
a3c062db | 620 | |
120b0122 BM |
621 | if (t->current_stream) { |
622 | *stream = &t->current_stream->parent; | |
623 | return 0; | |
624 | } | |
a3c062db | 625 | |
120b0122 BM |
626 | giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); |
627 | return -1; | |
628 | } | |
629 | ||
05f58131 | 630 | static int ssh_receivepack_ls( |
120b0122 BM |
631 | ssh_subtransport *t, |
632 | const char *url, | |
633 | git_smart_subtransport_stream **stream) | |
634 | { | |
d4256ed5 CMN |
635 | const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; |
636 | ||
637 | if (_git_ssh_setup_conn(t, url, cmd, stream) < 0) | |
120b0122 | 638 | return -1; |
a3c062db | 639 | |
120b0122 BM |
640 | return 0; |
641 | } | |
642 | ||
05f58131 | 643 | static int ssh_receivepack( |
8ae55d94 BM |
644 | ssh_subtransport *t, |
645 | const char *url, | |
646 | git_smart_subtransport_stream **stream) | |
297758dc BM |
647 | { |
648 | GIT_UNUSED(url); | |
a3c062db | 649 | |
297758dc BM |
650 | if (t->current_stream) { |
651 | *stream = &t->current_stream->parent; | |
652 | return 0; | |
653 | } | |
a3c062db | 654 | |
297758dc BM |
655 | giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK"); |
656 | return -1; | |
657 | } | |
658 | ||
67a7136c | 659 | static int _ssh_action( |
8ae55d94 BM |
660 | git_smart_subtransport_stream **stream, |
661 | git_smart_subtransport *subtransport, | |
662 | const char *url, | |
663 | git_smart_service_t action) | |
297758dc BM |
664 | { |
665 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
a3c062db | 666 | |
297758dc BM |
667 | switch (action) { |
668 | case GIT_SERVICE_UPLOADPACK_LS: | |
05f58131 | 669 | return ssh_uploadpack_ls(t, url, stream); |
a3c062db | 670 | |
297758dc | 671 | case GIT_SERVICE_UPLOADPACK: |
05f58131 | 672 | return ssh_uploadpack(t, url, stream); |
a3c062db | 673 | |
297758dc | 674 | case GIT_SERVICE_RECEIVEPACK_LS: |
05f58131 | 675 | return ssh_receivepack_ls(t, url, stream); |
a3c062db | 676 | |
297758dc | 677 | case GIT_SERVICE_RECEIVEPACK: |
05f58131 | 678 | return ssh_receivepack(t, url, stream); |
297758dc | 679 | } |
a3c062db | 680 | |
297758dc BM |
681 | *stream = NULL; |
682 | return -1; | |
683 | } | |
684 | ||
67a7136c | 685 | static int _ssh_close(git_smart_subtransport *subtransport) |
297758dc BM |
686 | { |
687 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
a3c062db | 688 | |
297758dc | 689 | assert(!t->current_stream); |
a3c062db | 690 | |
297758dc | 691 | GIT_UNUSED(t); |
a3c062db | 692 | |
297758dc BM |
693 | return 0; |
694 | } | |
695 | ||
67a7136c | 696 | static void _ssh_free(git_smart_subtransport *subtransport) |
297758dc BM |
697 | { |
698 | ssh_subtransport *t = (ssh_subtransport *) subtransport; | |
a3c062db | 699 | |
297758dc | 700 | assert(!t->current_stream); |
a3c062db | 701 | |
d4256ed5 CMN |
702 | git__free(t->cmd_uploadpack); |
703 | git__free(t->cmd_receivepack); | |
297758dc BM |
704 | git__free(t); |
705 | } | |
22618906 CMN |
706 | |
707 | #define SSH_AUTH_PUBLICKEY "publickey" | |
708 | #define SSH_AUTH_PASSWORD "password" | |
709 | #define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" | |
710 | ||
ccb85c8f | 711 | static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) |
22618906 | 712 | { |
22618906 CMN |
713 | const char *list, *ptr; |
714 | ||
715 | *out = 0; | |
716 | ||
ccb85c8f | 717 | list = libssh2_userauth_list(session, username, strlen(username)); |
22618906 CMN |
718 | |
719 | /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ | |
ccb85c8f CMN |
720 | if (list == NULL && !libssh2_userauth_authenticated(session)) |
721 | return -1; | |
22618906 CMN |
722 | |
723 | ptr = list; | |
724 | while (ptr) { | |
725 | if (*ptr == ',') | |
726 | ptr++; | |
727 | ||
728 | if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { | |
729 | *out |= GIT_CREDTYPE_SSH_KEY; | |
730 | *out |= GIT_CREDTYPE_SSH_CUSTOM; | |
731 | ptr += strlen(SSH_AUTH_PUBLICKEY); | |
732 | continue; | |
733 | } | |
734 | ||
735 | if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { | |
736 | *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT; | |
737 | ptr += strlen(SSH_AUTH_PASSWORD); | |
738 | continue; | |
739 | } | |
740 | ||
741 | if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { | |
742 | *out |= GIT_CREDTYPE_SSH_INTERACTIVE; | |
743 | ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); | |
744 | continue; | |
745 | } | |
746 | ||
747 | /* Skipt it if we don't know it */ | |
748 | ptr = strchr(ptr, ','); | |
749 | } | |
750 | ||
22618906 | 751 | return 0; |
22618906 | 752 | } |
a3c062db | 753 | #endif |
297758dc | 754 | |
a3c062db RB |
755 | int git_smart_subtransport_ssh( |
756 | git_smart_subtransport **out, git_transport *owner) | |
297758dc | 757 | { |
a3c062db | 758 | #ifdef GIT_SSH |
297758dc | 759 | ssh_subtransport *t; |
a3c062db RB |
760 | |
761 | assert(out); | |
762 | ||
297758dc BM |
763 | t = git__calloc(sizeof(ssh_subtransport), 1); |
764 | GITERR_CHECK_ALLOC(t); | |
a3c062db | 765 | |
f7158cd7 | 766 | t->owner = (transport_smart *)owner; |
67a7136c BM |
767 | t->parent.action = _ssh_action; |
768 | t->parent.close = _ssh_close; | |
769 | t->parent.free = _ssh_free; | |
a3c062db | 770 | |
297758dc BM |
771 | *out = (git_smart_subtransport *) t; |
772 | return 0; | |
a3c062db RB |
773 | #else |
774 | GIT_UNUSED(owner); | |
775 | ||
776 | assert(out); | |
777 | *out = NULL; | |
574b86b7 | 778 | |
a3c062db RB |
779 | giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support"); |
780 | return -1; | |
574b86b7 | 781 | #endif |
a3c062db | 782 | } |
d4256ed5 CMN |
783 | |
784 | int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) | |
785 | { | |
786 | #ifdef GIT_SSH | |
787 | git_strarray *paths = (git_strarray *) payload; | |
788 | git_transport *transport; | |
789 | transport_smart *smart; | |
790 | ssh_subtransport *t; | |
791 | int error; | |
792 | git_smart_subtransport_definition ssh_definition = { | |
793 | git_smart_subtransport_ssh, | |
794 | 0, /* no RPC */ | |
795 | }; | |
796 | ||
797 | if (paths->count != 2) { | |
798 | giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings"); | |
799 | return GIT_EINVALIDSPEC; | |
800 | } | |
801 | ||
802 | if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) | |
803 | return error; | |
804 | ||
805 | smart = (transport_smart *) transport; | |
806 | t = (ssh_subtransport *) smart->wrapped; | |
807 | ||
808 | t->cmd_uploadpack = git__strdup(paths->strings[0]); | |
809 | GITERR_CHECK_ALLOC(t->cmd_uploadpack); | |
810 | t->cmd_receivepack = git__strdup(paths->strings[1]); | |
811 | GITERR_CHECK_ALLOC(t->cmd_receivepack); | |
812 | ||
813 | *out = transport; | |
814 | return 0; | |
815 | #else | |
816 | GIT_UNUSED(owner); | |
8baeb8a4 | 817 | GIT_UNUSED(payload); |
d4256ed5 CMN |
818 | |
819 | assert(out); | |
820 | *out = NULL; | |
821 | ||
822 | giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support"); | |
823 | return -1; | |
824 | #endif | |
825 | } |