]> git.proxmox.com Git - libgit2.git/blame - src/transports/ssh.c
transport: always call the certificate check callback
[libgit2.git] / src / transports / ssh.c
CommitLineData
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 22static const char prefix_ssh[] = "ssh://";
297758dc
BM
23static const char cmd_uploadpack[] = "git-upload-pack";
24static const char cmd_receivepack[] = "git-receive-pack";
25
26typedef 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
36typedef 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 45static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
22618906 46
b622aabe
ES
47static 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 60static 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 90static 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
107cleanup:
108 git_buf_free(&request);
109 return error;
110}
111
8ae55d94
BM
112static 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
136static 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 165static 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
195static 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 225static 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
257static 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
294shutdown:
295 libssh2_agent_disconnect(agent);
296 libssh2_agent_free(agent);
297
298 return rc;
299}
300
c0cef9e0
BM
301static 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
371static 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 408static 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 440static 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 582on_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 604static 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 614static 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 630static 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 643static 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 659static 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 685static 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 696static 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 711static 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
755int 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
784int 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}