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