source src/transports/ssh.c
Line | Flow | Count | Block(s) | Source |
---|---|---|---|---|
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 | - | |||
8 | - | #include "ssh.h" | ||
9 | - | |||
10 | - | #ifdef GIT_SSH | ||
11 | - | #include <libssh2.h> | ||
12 | - | #endif | ||
13 | - | |||
14 | - | #include "global.h" | ||
15 | - | #include "git2.h" | ||
16 | - | #include "buffer.h" | ||
17 | - | #include "net.h" | ||
18 | - | #include "netops.h" | ||
19 | - | #include "smart.h" | ||
20 | - | #include "streams/socket.h" | ||
21 | - | |||
22 | - | #include "git2/credential.h" | ||
23 | - | #include "git2/sys/credential.h" | ||
24 | - | |||
25 | - | #ifdef GIT_SSH | ||
26 | - | |||
27 | - | #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) | ||
28 | - | |||
29 | - | static const char *ssh_prefixes[] = { "ssh://", "ssh+git://", "git+ssh://" }; | ||
30 | - | |||
31 | - | static const char cmd_uploadpack[] = "git-upload-pack"; | ||
32 | - | static const char cmd_receivepack[] = "git-receive-pack"; | ||
33 | - | |||
34 | - | typedef struct { | ||
35 | - | git_smart_subtransport_stream parent; | ||
36 | - | git_stream *io; | ||
37 | - | LIBSSH2_SESSION *session; | ||
38 | - | LIBSSH2_CHANNEL *channel; | ||
39 | - | const char *cmd; | ||
40 | - | char *url; | ||
41 | - | unsigned sent_command : 1; | ||
42 | - | } ssh_stream; | ||
43 | - | |||
44 | - | typedef struct { | ||
45 | - | git_smart_subtransport parent; | ||
46 | - | transport_smart *owner; | ||
47 | - | ssh_stream *current_stream; | ||
48 | - | git_credential *cred; | ||
49 | - | char *cmd_uploadpack; | ||
50 | - | char *cmd_receivepack; | ||
51 | - | } ssh_subtransport; | ||
52 | - | |||
53 | - | static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); | ||
54 | - | |||
55 | - | static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) | ||
56 | - | { | ||
57 | - | char *ssherr; | ||
58 | - | libssh2_session_last_error(session, &ssherr, NULL, 0); | ||
59 | - | |||
60 | - | git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); | ||
61 | - | } | ||
62 | - | |||
63 | - | /* | ||
64 | - | * Create a git protocol request. | ||
65 | - | * | ||
66 | - | * For example: git-upload-pack '/libgit2/libgit2' | ||
67 | - | */ | ||
68 | - | static int gen_proto(git_buf *request, const char *cmd, const char *url) | ||
69 | - | { | ||
70 | - | const char *repo; | ||
71 | - | int len; | ||
72 | - | size_t i; | ||
73 | - | |||
74 | - | for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) { | ||
75 | - | const char *p = ssh_prefixes[i]; | ||
76 | - | |||
77 | - | if (!git__prefixcmp(url, p)) { | ||
78 | - | url = url + strlen(p); | ||
79 | - | repo = strchr(url, '/'); | ||
80 | - | if (repo && repo[1] == '~') | ||
81 | - | ++repo; | ||
82 | - | |||
83 | - | goto done; | ||
84 | - | } | ||
85 | - | } | ||
86 | - | repo = strchr(url, ':'); | ||
87 | - | if (repo) repo++; | ||
88 | - | |||
89 | - | done: | ||
90 | - | if (!repo) { | ||
91 | - | git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); | ||
92 | - | return -1; | ||
93 | - | } | ||
94 | - | |||
95 | - | len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1; | ||
96 | - | |||
97 | - | git_buf_grow(request, len); | ||
98 | - | git_buf_puts(request, cmd); | ||
99 | - | git_buf_puts(request, " '"); | ||
100 | - | git_buf_decode_percent(request, repo, strlen(repo)); | ||
101 | - | git_buf_puts(request, "'"); | ||
102 | - | |||
103 | - | if (git_buf_oom(request)) | ||
104 | - | return -1; | ||
105 | - | |||
106 | - | return 0; | ||
107 | - | } | ||
108 | - | |||
109 | - | static int send_command(ssh_stream *s) | ||
110 | - | { | ||
111 | - | int error; | ||
112 | - | git_buf request = GIT_BUF_INIT; | ||
113 | - | |||
114 | - | error = gen_proto(&request, s->cmd, s->url); | ||
115 | - | if (error < 0) | ||
116 | - | goto cleanup; | ||
117 | - | |||
118 | - | error = libssh2_channel_exec(s->channel, request.ptr); | ||
119 | - | if (error < LIBSSH2_ERROR_NONE) { | ||
120 | - | ssh_error(s->session, "SSH could not execute request"); | ||
121 | - | goto cleanup; | ||
122 | - | } | ||
123 | - | |||
124 | - | s->sent_command = 1; | ||
125 | - | |||
126 | - | cleanup: | ||
127 | - | git_buf_dispose(&request); | ||
128 | - | return error; | ||
129 | - | } | ||
130 | - | |||
131 | - | static int ssh_stream_read( | ||
132 | - | git_smart_subtransport_stream *stream, | ||
133 | - | char *buffer, | ||
134 | - | size_t buf_size, | ||
135 | - | size_t *bytes_read) | ||
136 | - | { | ||
137 | - | int rc; | ||
138 | - | ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); | ||
139 | - | |||
140 | - | *bytes_read = 0; | ||
141 | - | |||
142 | - | if (!s->sent_command && send_command(s) < 0) | ||
143 | - | return -1; | ||
144 | - | |||
145 | - | if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { | ||
146 | - | ssh_error(s->session, "SSH could not read data"); | ||
147 | - | return -1; | ||
148 | - | } | ||
149 | - | |||
150 | - | /* | ||
151 | - | * If we can't get anything out of stdout, it's typically a | ||
152 | - | * not-found error, so read from stderr and signal EOF on | ||
153 | - | * stderr. | ||
154 | - | */ | ||
155 | - | if (rc == 0) { | ||
156 | - | if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { | ||
157 | - | git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); | ||
158 | - | return GIT_EEOF; | ||
159 | - | } else if (rc < LIBSSH2_ERROR_NONE) { | ||
160 | - | ssh_error(s->session, "SSH could not read stderr"); | ||
161 | - | return -1; | ||
162 | - | } | ||
163 | - | } | ||
164 | - | |||
165 | - | |||
166 | - | *bytes_read = rc; | ||
167 | - | |||
168 | - | return 0; | ||
169 | - | } | ||
170 | - | |||
171 | - | static int ssh_stream_write( | ||
172 | - | git_smart_subtransport_stream *stream, | ||
173 | - | const char *buffer, | ||
174 | - | size_t len) | ||
175 | - | { | ||
176 | - | ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); | ||
177 | - | size_t off = 0; | ||
178 | - | ssize_t ret = 0; | ||
179 | - | |||
180 | - | if (!s->sent_command && send_command(s) < 0) | ||
181 | - | return -1; | ||
182 | - | |||
183 | - | do { | ||
184 | - | ret = libssh2_channel_write(s->channel, buffer + off, len - off); | ||
185 | - | if (ret < 0) | ||
186 | - | break; | ||
187 | - | |||
188 | - | off += ret; | ||
189 | - | |||
190 | - | } while (off < len); | ||
191 | - | |||
192 | - | if (ret < 0) { | ||
193 | - | ssh_error(s->session, "SSH could not write data"); | ||
194 | - | return -1; | ||
195 | - | } | ||
196 | - | |||
197 | - | return 0; | ||
198 | - | } | ||
199 | - | |||
200 | - | static void ssh_stream_free(git_smart_subtransport_stream *stream) | ||
201 | - | { | ||
202 | - | ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); | ||
203 | - | ssh_subtransport *t; | ||
204 | - | |||
205 | - | if (!stream) | ||
206 | - | return; | ||
207 | - | |||
208 | - | t = OWNING_SUBTRANSPORT(s); | ||
209 | - | t->current_stream = NULL; | ||
210 | - | |||
211 | - | if (s->channel) { | ||
212 | - | libssh2_channel_close(s->channel); | ||
213 | - | libssh2_channel_free(s->channel); | ||
214 | - | s->channel = NULL; | ||
215 | - | } | ||
216 | - | |||
217 | - | if (s->session) { | ||
218 | - | libssh2_session_disconnect(s->session, "closing transport"); | ||
219 | - | libssh2_session_free(s->session); | ||
220 | - | s->session = NULL; | ||
221 | - | } | ||
222 | - | |||
223 | - | if (s->io) { | ||
224 | - | git_stream_close(s->io); | ||
225 | - | git_stream_free(s->io); | ||
226 | - | s->io = NULL; | ||
227 | - | } | ||
228 | - | |||
229 | - | git__free(s->url); | ||
230 | - | git__free(s); | ||
231 | - | } | ||
232 | - | |||
233 | - | static int ssh_stream_alloc( | ||
234 | - | ssh_subtransport *t, | ||
235 | - | const char *url, | ||
236 | - | const char *cmd, | ||
237 | - | git_smart_subtransport_stream **stream) | ||
238 | - | { | ||
239 | - | ssh_stream *s; | ||
240 | - | |||
241 | - | assert(stream); | ||
242 | - | |||
243 | - | s = git__calloc(sizeof(ssh_stream), 1); | ||
244 | - | GIT_ERROR_CHECK_ALLOC(s); | ||
245 | - | |||
246 | - | s->parent.subtransport = &t->parent; | ||
247 | - | s->parent.read = ssh_stream_read; | ||
248 | - | s->parent.write = ssh_stream_write; | ||
249 | - | s->parent.free = ssh_stream_free; | ||
250 | - | |||
251 | - | s->cmd = cmd; | ||
252 | - | |||
253 | - | s->url = git__strdup(url); | ||
254 | - | if (!s->url) { | ||
255 | - | git__free(s); | ||
256 | - | return -1; | ||
257 | - | } | ||
258 | - | |||
259 | - | *stream = &s->parent; | ||
260 | - | return 0; | ||
261 | - | } | ||
262 | - | |||
263 | - | static int git_ssh_extract_url_parts( | ||
264 | - | git_net_url *urldata, | ||
265 | - | const char *url) | ||
266 | - | { | ||
267 | - | char *colon, *at; | ||
268 | - | const char *start; | ||
269 | - | |||
270 | - | colon = strchr(url, ':'); | ||
271 | - | |||
272 | - | |||
273 | - | at = strchr(url, '@'); | ||
274 | - | if (at) { | ||
275 | - | start = at + 1; | ||
276 | - | urldata->username = git__substrdup(url, at - url); | ||
277 | - | GIT_ERROR_CHECK_ALLOC(urldata->username); | ||
278 | - | } else { | ||
279 | - | start = url; | ||
280 | - | urldata->username = NULL; | ||
281 | - | } | ||
282 | - | |||
283 | - | if (colon == NULL || (colon < start)) { | ||
284 | - | git_error_set(GIT_ERROR_NET, "malformed URL"); | ||
285 | - | return -1; | ||
286 | - | } | ||
287 | - | |||
288 | - | urldata->host = git__substrdup(start, colon - start); | ||
289 | - | GIT_ERROR_CHECK_ALLOC(urldata->host); | ||
290 | - | |||
291 | - | return 0; | ||
292 | - | } | ||
293 | - | |||
294 | - | static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { | ||
295 | - | int rc = LIBSSH2_ERROR_NONE; | ||
296 | - | |||
297 | - | struct libssh2_agent_publickey *curr, *prev = NULL; | ||
298 | - | |||
299 | - | LIBSSH2_AGENT *agent = libssh2_agent_init(session); | ||
300 | - | |||
301 | - | if (agent == NULL) | ||
302 | - | return -1; | ||
303 | - | |||
304 | - | rc = libssh2_agent_connect(agent); | ||
305 | - | |||
306 | - | if (rc != LIBSSH2_ERROR_NONE) | ||
307 | - | goto shutdown; | ||
308 | - | |||
309 | - | rc = libssh2_agent_list_identities(agent); | ||
310 | - | |||
311 | - | if (rc != LIBSSH2_ERROR_NONE) | ||
312 | - | goto shutdown; | ||
313 | - | |||
314 | - | while (1) { | ||
315 | - | rc = libssh2_agent_get_identity(agent, &curr, prev); | ||
316 | - | |||
317 | - | if (rc < 0) | ||
318 | - | goto shutdown; | ||
319 | - | |||
320 | - | /* rc is set to 1 whenever the ssh agent ran out of keys to check. | ||
321 | - | * Set the error code to authentication failure rather than erroring | ||
322 | - | * out with an untranslatable error code. | ||
323 | - | */ | ||
324 | - | if (rc == 1) { | ||
325 | - | rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; | ||
326 | - | goto shutdown; | ||
327 | - | } | ||
328 | - | |||
329 | - | rc = libssh2_agent_userauth(agent, c->username, curr); | ||
330 | - | |||
331 | - | if (rc == 0) | ||
332 | - | break; | ||
333 | - | |||
334 | - | prev = curr; | ||
335 | - | } | ||
336 | - | |||
337 | - | shutdown: | ||
338 | - | |||
339 | - | if (rc != LIBSSH2_ERROR_NONE) | ||
340 | - | ssh_error(session, "error authenticating"); | ||
341 | - | |||
342 | - | libssh2_agent_disconnect(agent); | ||
343 | - | libssh2_agent_free(agent); | ||
344 | - | |||
345 | - | return rc; | ||
346 | - | } | ||
347 | - | |||
348 | - | static int _git_ssh_authenticate_session( | ||
349 | - | LIBSSH2_SESSION *session, | ||
350 | - | git_credential *cred) | ||
351 | - | { | ||
352 | - | int rc; | ||
353 | - | |||
354 | - | do { | ||
355 | - | git_error_clear(); | ||
356 | - | switch (cred->credtype) { | ||
357 | - | case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { | ||
358 | - | git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; | ||
359 | - | rc = libssh2_userauth_password(session, c->username, c->password); | ||
360 | - | break; | ||
361 | - | } | ||
362 | - | case GIT_CREDENTIAL_SSH_KEY: { | ||
363 | - | git_credential_ssh_key *c = (git_credential_ssh_key *)cred; | ||
364 | - | |||
365 | - | if (c->privatekey) | ||
366 | - | rc = libssh2_userauth_publickey_fromfile( | ||
367 | - | session, c->username, c->publickey, | ||
368 | - | c->privatekey, c->passphrase); | ||
369 | - | else | ||
370 | - | rc = ssh_agent_auth(session, c); | ||
371 | - | |||
372 | - | break; | ||
373 | - | } | ||
374 | - | case GIT_CREDENTIAL_SSH_CUSTOM: { | ||
375 | - | git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; | ||
376 | - | |||
377 | - | rc = libssh2_userauth_publickey( | ||
378 | - | session, c->username, (const unsigned char *)c->publickey, | ||
379 | - | c->publickey_len, c->sign_callback, &c->payload); | ||
380 | - | break; | ||
381 | - | } | ||
382 | - | case GIT_CREDENTIAL_SSH_INTERACTIVE: { | ||
383 | - | void **abstract = libssh2_session_abstract(session); | ||
384 | - | git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; | ||
385 | - | |||
386 | - | /* ideally, we should be able to set this by calling | ||
387 | - | * libssh2_session_init_ex() instead of libssh2_session_init(). | ||
388 | - | * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() | ||
389 | - | * allows you to pass the `abstract` as part of the call, whereas | ||
390 | - | * libssh2_userauth_keyboard_interactive() does not! | ||
391 | - | * | ||
392 | - | * The only way to set the `abstract` pointer is by calling | ||
393 | - | * libssh2_session_abstract(), which will replace the existing | ||
394 | - | * pointer as is done below. This is safe for now (at time of writing), | ||
395 | - | * but may not be valid in future. | ||
396 | - | */ | ||
397 | - | *abstract = c->payload; | ||
398 | - | |||
399 | - | rc = libssh2_userauth_keyboard_interactive( | ||
400 | - | session, c->username, c->prompt_callback); | ||
401 | - | break; | ||
402 | - | } | ||
403 | - | #ifdef GIT_SSH_MEMORY_CREDENTIALS | ||
404 | - | case GIT_CREDENTIAL_SSH_MEMORY: { | ||
405 | - | git_credential_ssh_key *c = (git_credential_ssh_key *)cred; | ||
406 | - | |||
407 | - | assert(c->username); | ||
408 | - | assert(c->privatekey); | ||
409 | - | |||
410 | - | rc = libssh2_userauth_publickey_frommemory( | ||
411 | - | session, | ||
412 | - | c->username, | ||
413 | - | strlen(c->username), | ||
414 | - | c->publickey, | ||
415 | - | c->publickey ? strlen(c->publickey) : 0, | ||
416 | - | c->privatekey, | ||
417 | - | strlen(c->privatekey), | ||
418 | - | c->passphrase); | ||
419 | - | break; | ||
420 | - | } | ||
421 | - | #endif | ||
422 | - | default: | ||
423 | - | rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; | ||
424 | - | } | ||
425 | - | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | ||
426 | - | |||
427 | - | if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || | ||
428 | - | rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || | ||
429 | - | rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) | ||
430 | - | return GIT_EAUTH; | ||
431 | - | |||
432 | - | if (rc != LIBSSH2_ERROR_NONE) { | ||
433 | - | if (!git_error_last()) | ||
434 | - | ssh_error(session, "Failed to authenticate SSH session"); | ||
435 | - | return -1; | ||
436 | - | } | ||
437 | - | |||
438 | - | return 0; | ||
439 | - | } | ||
440 | - | |||
441 | - | static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) | ||
442 | - | { | ||
443 | - | int error, no_callback = 0; | ||
444 | - | git_credential *cred = NULL; | ||
445 | - | |||
446 | - | if (!t->owner->cred_acquire_cb) { | ||
447 | - | no_callback = 1; | ||
448 | - | } else { | ||
449 | - | error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods, | ||
450 | - | t->owner->cred_acquire_payload); | ||
451 | - | |||
452 | - | if (error == GIT_PASSTHROUGH) { | ||
453 | - | no_callback = 1; | ||
454 | - | } else if (error < 0) { | ||
455 | - | return error; | ||
456 | - | } else if (!cred) { | ||
457 | - | git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); | ||
458 | - | return -1; | ||
459 | - | } | ||
460 | - | } | ||
461 | - | |||
462 | - | if (no_callback) { | ||
463 | - | git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); | ||
464 | - | return -1; | ||
465 | - | } | ||
466 | - | |||
467 | - | if (!(cred->credtype & auth_methods)) { | ||
468 | - | cred->free(cred); | ||
469 | - | git_error_set(GIT_ERROR_SSH, "callback returned unsupported credentials type"); | ||
470 | - | return -1; | ||
471 | - | } | ||
472 | - | |||
473 | - | *out = cred; | ||
474 | - | |||
475 | - | return 0; | ||
476 | - | } | ||
477 | - | |||
478 | - | static int _git_ssh_session_create( | ||
479 | - | LIBSSH2_SESSION** session, | ||
480 | - | git_stream *io) | ||
481 | - | { | ||
482 | - | int rc = 0; | ||
483 | - | LIBSSH2_SESSION* s; | ||
484 | - | git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); | ||
485 | - | |||
486 | - | assert(session); | ||
487 | - | |||
488 | - | s = libssh2_session_init(); | ||
489 | - | if (!s) { | ||
490 | - | git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); | ||
491 | - | return -1; | ||
492 | - | } | ||
493 | - | |||
494 | - | do { | ||
495 | - | rc = libssh2_session_handshake(s, socket->s); | ||
496 | - | } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); | ||
497 | - | |||
498 | - | if (rc != LIBSSH2_ERROR_NONE) { | ||
499 | - | ssh_error(s, "failed to start SSH session"); | ||
500 | - | libssh2_session_free(s); | ||
501 | - | return -1; | ||
502 | - | } | ||
503 | - | |||
504 | - | libssh2_session_set_blocking(s, 1); | ||
505 | - | |||
506 | - | *session = s; | ||
507 | - | |||
508 | - | return 0; | ||
509 | - | } | ||
510 | - | |||
511 | - | #define SSH_DEFAULT_PORT "22" | ||
512 | - | |||
513 | - | static int _git_ssh_setup_conn( | ||
514 | - | ssh_subtransport *t, | ||
515 | - | const char *url, | ||
516 | - | const char *cmd, | ||
517 | - | git_smart_subtransport_stream **stream) | ||
518 | - | { | ||
519 | - | git_net_url urldata = GIT_NET_URL_INIT; | ||
520 | - | int auth_methods, error = 0; | ||
521 | - | size_t i; | ||
522 | - | ssh_stream *s; | ||
523 | - | git_credential *cred = NULL; | ||
524 | - | LIBSSH2_SESSION* session=NULL; | ||
525 | - | LIBSSH2_CHANNEL* channel=NULL; | ||
526 | - | |||
527 | - | t->current_stream = NULL; | ||
528 | - | |||
529 | - | *stream = NULL; | ||
530 | - | if (ssh_stream_alloc(t, url, cmd, stream) < 0) | ||
531 | - | return -1; | ||
532 | - | |||
533 | - | s = (ssh_stream *)*stream; | ||
534 | - | s->session = NULL; | ||
535 | - | s->channel = NULL; | ||
536 | - | |||
537 | - | for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) { | ||
538 | - | const char *p = ssh_prefixes[i]; | ||
539 | - | |||
540 | - | if (!git__prefixcmp(url, p)) { | ||
541 | - | if ((error = git_net_url_parse(&urldata, url)) < 0) | ||
542 | - | goto done; | ||
543 | - | |||
544 | - | goto post_extract; | ||
545 | - | } | ||
546 | - | } | ||
547 | - | if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0) | ||
548 | - | goto done; | ||
549 | - | |||
550 | - | if (urldata.port == NULL) | ||
551 | - | urldata.port = git__strdup(SSH_DEFAULT_PORT); | ||
552 | - | |||
553 | - | GIT_ERROR_CHECK_ALLOC(urldata.port); | ||
554 | - | |||
555 | - | post_extract: | ||
556 | - | if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 || | ||
557 | - | (error = git_stream_connect(s->io)) < 0) | ||
558 | - | goto done; | ||
559 | - | |||
560 | - | if ((error = _git_ssh_session_create(&session, s->io)) < 0) | ||
561 | - | goto done; | ||
562 | - | |||
563 | - | if (t->owner->certificate_check_cb != NULL) { | ||
564 | - | git_cert_hostkey cert = {{ 0 }}, *cert_ptr; | ||
565 | - | const char *key; | ||
566 | - | |||
567 | - | cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; | ||
568 | - | |||
569 | - | #ifdef LIBSSH2_HOSTKEY_HASH_SHA256 | ||
570 | - | key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); | ||
571 | - | if (key != NULL) { | ||
572 | - | cert.type |= GIT_CERT_SSH_SHA256; | ||
573 | - | memcpy(&cert.hash_sha256, key, 32); | ||
574 | - | } | ||
575 | - | #endif | ||
576 | - | |||
577 | - | key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); | ||
578 | - | if (key != NULL) { | ||
579 | - | cert.type |= GIT_CERT_SSH_SHA1; | ||
580 | - | memcpy(&cert.hash_sha1, key, 20); | ||
581 | - | } | ||
582 | - | |||
583 | - | key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); | ||
584 | - | if (key != NULL) { | ||
585 | - | cert.type |= GIT_CERT_SSH_MD5; | ||
586 | - | memcpy(&cert.hash_md5, key, 16); | ||
587 | - | } | ||
588 | - | |||
589 | - | if (cert.type == 0) { | ||
590 | - | git_error_set(GIT_ERROR_SSH, "unable to get the host key"); | ||
591 | - | error = -1; | ||
592 | - | goto done; | ||
593 | - | } | ||
594 | - | |||
595 | - | /* We don't currently trust any hostkeys */ | ||
596 | - | git_error_clear(); | ||
597 | - | |||
598 | - | cert_ptr = &cert; | ||
599 | - | |||
600 | - | error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload); | ||
601 | - | |||
602 | - | if (error < 0 && error != GIT_PASSTHROUGH) { | ||
603 | - | if (!git_error_last()) | ||
604 | - | git_error_set(GIT_ERROR_NET, "user cancelled hostkey check"); | ||
605 | - | |||
606 | - | goto done; | ||
607 | - | } | ||
608 | - | } | ||
609 | - | |||
610 | - | /* we need the username to ask for auth methods */ | ||
611 | - | if (!urldata.username) { | ||
612 | - | if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) | ||
613 | - | goto done; | ||
614 | - | |||
615 | - | urldata.username = git__strdup(((git_credential_username *) cred)->username); | ||
616 | - | cred->free(cred); | ||
617 | - | cred = NULL; | ||
618 | - | if (!urldata.username) | ||
619 | - | goto done; | ||
620 | - | } else if (urldata.username && urldata.password) { | ||
621 | - | if ((error = git_credential_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0) | ||
622 | - | goto done; | ||
623 | - | } | ||
624 | - | |||
625 | - | if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0) | ||
626 | - | goto done; | ||
627 | - | |||
628 | - | error = GIT_EAUTH; | ||
629 | - | /* if we already have something to try */ | ||
630 | - | if (cred && auth_methods & cred->credtype) | ||
631 | - | error = _git_ssh_authenticate_session(session, cred); | ||
632 | - | |||
633 | - | while (error == GIT_EAUTH) { | ||
634 | - | if (cred) { | ||
635 | - | cred->free(cred); | ||
636 | - | cred = NULL; | ||
637 | - | } | ||
638 | - | |||
639 | - | if ((error = request_creds(&cred, t, urldata.username, auth_methods)) < 0) | ||
640 | - | goto done; | ||
641 | - | |||
642 | - | if (strcmp(urldata.username, git_credential_get_username(cred))) { | ||
643 | - | git_error_set(GIT_ERROR_SSH, "username does not match previous request"); | ||
644 | - | error = -1; | ||
645 | - | goto done; | ||
646 | - | } | ||
647 | - | |||
648 | - | error = _git_ssh_authenticate_session(session, cred); | ||
649 | - | |||
650 | - | if (error == GIT_EAUTH) { | ||
651 | - | /* refresh auth methods */ | ||
652 | - | if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0) | ||
653 | - | goto done; | ||
654 | - | else | ||
655 | - | error = GIT_EAUTH; | ||
656 | - | } | ||
657 | - | } | ||
658 | - | |||
659 | - | if (error < 0) | ||
660 | - | goto done; | ||
661 | - | |||
662 | - | channel = libssh2_channel_open_session(session); | ||
663 | - | if (!channel) { | ||
664 | - | error = -1; | ||
665 | - | ssh_error(session, "Failed to open SSH channel"); | ||
666 | - | goto done; | ||
667 | - | } | ||
668 | - | |||
669 | - | libssh2_channel_set_blocking(channel, 1); | ||
670 | - | |||
671 | - | s->session = session; | ||
672 | - | s->channel = channel; | ||
673 | - | |||
674 | - | t->current_stream = s; | ||
675 | - | |||
676 | - | done: | ||
677 | - | if (error < 0) { | ||
678 | - | ssh_stream_free(*stream); | ||
679 | - | |||
680 | - | if (session) | ||
681 | - | libssh2_session_free(session); | ||
682 | - | } | ||
683 | - | |||
684 | - | if (cred) | ||
685 | - | cred->free(cred); | ||
686 | - | |||
687 | - | git_net_url_dispose(&urldata); | ||
688 | - | |||
689 | - | return error; | ||
690 | - | } | ||
691 | - | |||
692 | - | static int ssh_uploadpack_ls( | ||
693 | - | ssh_subtransport *t, | ||
694 | - | const char *url, | ||
695 | - | git_smart_subtransport_stream **stream) | ||
696 | - | { | ||
697 | - | const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; | ||
698 | - | |||
699 | - | return _git_ssh_setup_conn(t, url, cmd, stream); | ||
700 | - | } | ||
701 | - | |||
702 | - | static int ssh_uploadpack( | ||
703 | - | ssh_subtransport *t, | ||
704 | - | const char *url, | ||
705 | - | git_smart_subtransport_stream **stream) | ||
706 | - | { | ||
707 | - | GIT_UNUSED(url); | ||
708 | - | |||
709 | - | if (t->current_stream) { | ||
710 | - | *stream = &t->current_stream->parent; | ||
711 | - | return 0; | ||
712 | - | } | ||
713 | - | |||
714 | - | git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); | ||
715 | - | return -1; | ||
716 | - | } | ||
717 | - | |||
718 | - | static int ssh_receivepack_ls( | ||
719 | - | ssh_subtransport *t, | ||
720 | - | const char *url, | ||
721 | - | git_smart_subtransport_stream **stream) | ||
722 | - | { | ||
723 | - | const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; | ||
724 | - | |||
725 | - | |||
726 | - | return _git_ssh_setup_conn(t, url, cmd, stream); | ||
727 | - | } | ||
728 | - | |||
729 | - | static int ssh_receivepack( | ||
730 | - | ssh_subtransport *t, | ||
731 | - | const char *url, | ||
732 | - | git_smart_subtransport_stream **stream) | ||
733 | - | { | ||
734 | - | GIT_UNUSED(url); | ||
735 | - | |||
736 | - | if (t->current_stream) { | ||
737 | - | *stream = &t->current_stream->parent; | ||
738 | - | return 0; | ||
739 | - | } | ||
740 | - | |||
741 | - | git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); | ||
742 | - | return -1; | ||
743 | - | } | ||
744 | - | |||
745 | - | static int _ssh_action( | ||
746 | - | git_smart_subtransport_stream **stream, | ||
747 | - | git_smart_subtransport *subtransport, | ||
748 | - | const char *url, | ||
749 | - | git_smart_service_t action) | ||
750 | - | { | ||
751 | - | ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); | ||
752 | - | |||
753 | - | switch (action) { | ||
754 | - | case GIT_SERVICE_UPLOADPACK_LS: | ||
755 | - | return ssh_uploadpack_ls(t, url, stream); | ||
756 | - | |||
757 | - | case GIT_SERVICE_UPLOADPACK: | ||
758 | - | return ssh_uploadpack(t, url, stream); | ||
759 | - | |||
760 | - | case GIT_SERVICE_RECEIVEPACK_LS: | ||
761 | - | return ssh_receivepack_ls(t, url, stream); | ||
762 | - | |||
763 | - | case GIT_SERVICE_RECEIVEPACK: | ||
764 | - | return ssh_receivepack(t, url, stream); | ||
765 | - | } | ||
766 | - | |||
767 | - | *stream = NULL; | ||
768 | - | return -1; | ||
769 | - | } | ||
770 | - | |||
771 | - | static int _ssh_close(git_smart_subtransport *subtransport) | ||
772 | - | { | ||
773 | - | ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); | ||
774 | - | |||
775 | - | assert(!t->current_stream); | ||
776 | - | |||
777 | - | GIT_UNUSED(t); | ||
778 | - | |||
779 | - | return 0; | ||
780 | - | } | ||
781 | - | |||
782 | - | static void _ssh_free(git_smart_subtransport *subtransport) | ||
783 | - | { | ||
784 | - | ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); | ||
785 | - | |||
786 | - | assert(!t->current_stream); | ||
787 | - | |||
788 | - | git__free(t->cmd_uploadpack); | ||
789 | - | git__free(t->cmd_receivepack); | ||
790 | - | git__free(t); | ||
791 | - | } | ||
792 | - | |||
793 | - | #define SSH_AUTH_PUBLICKEY "publickey" | ||
794 | - | #define SSH_AUTH_PASSWORD "password" | ||
795 | - | #define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" | ||
796 | - | |||
797 | - | static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) | ||
798 | - | { | ||
799 | - | const char *list, *ptr; | ||
800 | - | |||
801 | - | *out = 0; | ||
802 | - | |||
803 | - | list = libssh2_userauth_list(session, username, strlen(username)); | ||
804 | - | |||
805 | - | /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ | ||
806 | - | if (list == NULL && !libssh2_userauth_authenticated(session)) { | ||
807 | - | ssh_error(session, "Failed to retrieve list of SSH authentication methods"); | ||
808 | - | return -1; | ||
809 | - | } | ||
810 | - | |||
811 | - | ptr = list; | ||
812 | - | while (ptr) { | ||
813 | - | if (*ptr == ',') | ||
814 | - | ptr++; | ||
815 | - | |||
816 | - | if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { | ||
817 | - | *out |= GIT_CREDENTIAL_SSH_KEY; | ||
818 | - | *out |= GIT_CREDENTIAL_SSH_CUSTOM; | ||
819 | - | #ifdef GIT_SSH_MEMORY_CREDENTIALS | ||
820 | - | *out |= GIT_CREDENTIAL_SSH_MEMORY; | ||
821 | - | #endif | ||
822 | - | ptr += strlen(SSH_AUTH_PUBLICKEY); | ||
823 | - | continue; | ||
824 | - | } | ||
825 | - | |||
826 | - | if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { | ||
827 | - | *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; | ||
828 | - | ptr += strlen(SSH_AUTH_PASSWORD); | ||
829 | - | continue; | ||
830 | - | } | ||
831 | - | |||
832 | - | if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { | ||
833 | - | *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; | ||
834 | - | ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); | ||
835 | - | continue; | ||
836 | - | } | ||
837 | - | |||
838 | - | /* Skipt it if we don't know it */ | ||
839 | - | ptr = strchr(ptr, ','); | ||
840 | - | } | ||
841 | - | |||
842 | - | return 0; | ||
843 | - | } | ||
844 | - | #endif | ||
845 | - | |||
846 | ##### | 2 | int git_smart_subtransport_ssh( | |
847 | - | git_smart_subtransport **out, git_transport *owner, void *param) | ||
848 | - | { | ||
849 | - | #ifdef GIT_SSH | ||
850 | - | ssh_subtransport *t; | ||
851 | - | |||
852 | - | assert(out); | ||
853 | - | |||
854 | - | GIT_UNUSED(param); | ||
855 | - | |||
856 | - | t = git__calloc(sizeof(ssh_subtransport), 1); | ||
857 | - | GIT_ERROR_CHECK_ALLOC(t); | ||
858 | - | |||
859 | - | t->owner = (transport_smart *)owner; | ||
860 | - | t->parent.action = _ssh_action; | ||
861 | - | t->parent.close = _ssh_close; | ||
862 | - | t->parent.free = _ssh_free; | ||
863 | - | |||
864 | - | *out = (git_smart_subtransport *) t; | ||
865 | - | return 0; | ||
866 | - | #else | ||
867 | - | GIT_UNUSED(owner); | ||
868 | - | GIT_UNUSED(param); | ||
869 | - | |||
870 | ##### | 2,3 | assert(out); | |
871 | ##### | 4 | *out = NULL; | |
872 | - | |||
873 | ##### | 4 | git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); | |
874 | ##### | 5 | return -1; | |
875 | - | #endif | ||
876 | - | } | ||
877 | - | |||
878 | ##### | 2 | int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) | |
879 | - | { | ||
880 | - | #ifdef GIT_SSH | ||
881 | - | git_strarray *paths = (git_strarray *) payload; | ||
882 | - | git_transport *transport; | ||
883 | - | transport_smart *smart; | ||
884 | - | ssh_subtransport *t; | ||
885 | - | int error; | ||
886 | - | git_smart_subtransport_definition ssh_definition = { | ||
887 | - | git_smart_subtransport_ssh, | ||
888 | - | 0, /* no RPC */ | ||
889 | - | NULL, | ||
890 | - | }; | ||
891 | - | |||
892 | - | if (paths->count != 2) { | ||
893 | - | git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings"); | ||
894 | - | return GIT_EINVALIDSPEC; | ||
895 | - | } | ||
896 | - | |||
897 | - | if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) | ||
898 | - | return error; | ||
899 | - | |||
900 | - | smart = (transport_smart *) transport; | ||
901 | - | t = (ssh_subtransport *) smart->wrapped; | ||
902 | - | |||
903 | - | t->cmd_uploadpack = git__strdup(paths->strings[0]); | ||
904 | - | GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); | ||
905 | - | t->cmd_receivepack = git__strdup(paths->strings[1]); | ||
906 | - | GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); | ||
907 | - | |||
908 | - | *out = transport; | ||
909 | - | return 0; | ||
910 | - | #else | ||
911 | - | GIT_UNUSED(owner); | ||
912 | - | GIT_UNUSED(payload); | ||
913 | - | |||
914 | ##### | 2,3 | assert(out); | |
915 | ##### | 4 | *out = NULL; | |
916 | - | |||
917 | ##### | 4 | git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); | |
918 | ##### | 5 | return -1; | |
919 | - | #endif | ||
920 | - | } | ||
921 | - | |||
922 | - | #ifdef GIT_SSH | ||
923 | - | static void shutdown_ssh(void) | ||
924 | - | { | ||
925 | - | libssh2_exit(); | ||
926 | - | } | ||
927 | - | #endif | ||
928 | - | |||
929 | 9 | 2 | int git_transport_ssh_global_init(void) | |
930 | - | { | ||
931 | - | #ifdef GIT_SSH | ||
932 | - | if (libssh2_init(0) < 0) { | ||
933 | - | git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); | ||
934 | - | return -1; | ||
935 | - | } | ||
936 | - | |||
937 | - | git__on_shutdown(shutdown_ssh); | ||
938 | - | return 0; | ||
939 | - | |||
940 | - | #else | ||
941 | - | |||
942 | - | /* Nothing to initialize */ | ||
943 | 9 | 2 | return 0; | |
944 | - | |||
945 | - | #endif | ||
946 | - | } |