ssh.c 20.2 KB
Newer Older
Brad Morgan committed
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "ssh.h"

10 11 12 13
#ifdef GIT_SSH
#include <libssh2.h>
#endif

14
#include "runtime.h"
15
#include "net.h"
Brad Morgan committed
16
#include "netops.h"
Brad Morgan committed
17
#include "smart.h"
18
#include "streams/socket.h"
Brad Morgan committed
19

20 21
#include "git2/credential.h"
#include "git2/sys/credential.h"
22

23 24
#ifdef GIT_SSH

Brad Morgan committed
25 26 27 28 29 30 31
#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)

static const char cmd_uploadpack[] = "git-upload-pack";
static const char cmd_receivepack[] = "git-receive-pack";

typedef struct {
	git_smart_subtransport_stream parent;
32
	git_stream *io;
Brad Morgan committed
33 34
	LIBSSH2_SESSION *session;
	LIBSSH2_CHANNEL *channel;
Brad Morgan committed
35
	const char *cmd;
36
	git_net_url url;
Brad Morgan committed
37 38 39 40 41
	unsigned sent_command : 1;
} ssh_stream;

typedef struct {
	git_smart_subtransport parent;
Brad Morgan committed
42
	transport_smart *owner;
Brad Morgan committed
43
	ssh_stream *current_stream;
44
	git_credential *cred;
45 46
	char *cmd_uploadpack;
	char *cmd_receivepack;
Brad Morgan committed
47 48
} ssh_subtransport;

49
static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username);
50

51 52 53 54 55
static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
{
	char *ssherr;
	libssh2_session_last_error(session, &ssherr, NULL, 0);

56
	git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr);
57 58
}

Brad Morgan committed
59 60 61
/*
 * Create a git protocol request.
 *
Brad Morgan committed
62
 * For example: git-upload-pack '/libgit2/libgit2'
Brad Morgan committed
63
 */
64
static int gen_proto(git_str *request, const char *cmd, git_net_url *url)
Brad Morgan committed
65
{
66
	const char *repo;
67

68
	repo = url->path;
69

70 71
	if (repo && repo[0] == '/' && repo[1] == '~')
		repo++;
72

73
	if (!repo || !repo[0]) {
74
		git_error_set(GIT_ERROR_NET, "malformed git protocol URL");
75 76
		return -1;
	}
77

78 79
	git_str_puts(request, cmd);
	git_str_puts(request, " '");
80
	git_str_puts(request, repo);
81
	git_str_puts(request, "'");
82

83
	if (git_str_oom(request))
Brad Morgan committed
84
		return -1;
85

Brad Morgan committed
86 87 88
	return 0;
}

Brad Morgan committed
89
static int send_command(ssh_stream *s)
Brad Morgan committed
90 91
{
	int error;
92
	git_str request = GIT_STR_INIT;
93

94
	error = gen_proto(&request, s->cmd, &s->url);
Brad Morgan committed
95 96
	if (error < 0)
		goto cleanup;
97

98
	error = libssh2_channel_exec(s->channel, request.ptr);
99 100
	if (error < LIBSSH2_ERROR_NONE) {
		ssh_error(s->session, "SSH could not execute request");
Brad Morgan committed
101
		goto cleanup;
102
	}
103

Brad Morgan committed
104
	s->sent_command = 1;
105

Brad Morgan committed
106
cleanup:
107
	git_str_dispose(&request);
Brad Morgan committed
108 109 110
	return error;
}

Brad Morgan committed
111 112 113 114 115
static int ssh_stream_read(
	git_smart_subtransport_stream *stream,
	char *buffer,
	size_t buf_size,
	size_t *bytes_read)
Brad Morgan committed
116
{
117
	int rc;
118
	ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
119

Brad Morgan committed
120
	*bytes_read = 0;
121

Brad Morgan committed
122 123
	if (!s->sent_command && send_command(s) < 0)
		return -1;
124

125
	if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
126
		ssh_error(s->session, "SSH could not read data");
Brad Morgan committed
127
		return -1;
128
	}
129

130 131 132 133 134
	/*
	 * If we can't get anything out of stdout, it's typically a
	 * not-found error, so read from stderr and signal EOF on
	 * stderr.
	 */
135 136
	if (rc == 0) {
		if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) {
137
			git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer);
138 139 140 141 142
			return GIT_EEOF;
		} else if (rc < LIBSSH2_ERROR_NONE) {
			ssh_error(s->session, "SSH could not read stderr");
			return -1;
		}
143 144 145
	}


Brad Morgan committed
146
	*bytes_read = rc;
147

Brad Morgan committed
148 149 150
	return 0;
}

Brad Morgan committed
151 152 153 154
static int ssh_stream_write(
	git_smart_subtransport_stream *stream,
	const char *buffer,
	size_t len)
Brad Morgan committed
155
{
156
	ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
157 158
	size_t off = 0;
	ssize_t ret = 0;
159

Brad Morgan committed
160 161
	if (!s->sent_command && send_command(s) < 0)
		return -1;
162

163 164 165 166 167 168 169 170 171 172
	do {
		ret = libssh2_channel_write(s->channel, buffer + off, len - off);
		if (ret < 0)
			break;

		off += ret;

	} while (off < len);

	if (ret < 0) {
173
		ssh_error(s->session, "SSH could not write data");
Brad Morgan committed
174 175
		return -1;
	}
176

177
	return 0;
Brad Morgan committed
178 179
}

Brad Morgan committed
180
static void ssh_stream_free(git_smart_subtransport_stream *stream)
Brad Morgan committed
181
{
182
	ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent);
183
	ssh_subtransport *t;
184

185 186
	if (!stream)
		return;
187

188
	t = OWNING_SUBTRANSPORT(s);
Brad Morgan committed
189
	t->current_stream = NULL;
190

Brad Morgan committed
191 192
	if (s->channel) {
		libssh2_channel_close(s->channel);
193 194
		libssh2_channel_free(s->channel);
		s->channel = NULL;
Brad Morgan committed
195
	}
196

Brad Morgan committed
197
	if (s->session) {
198
		libssh2_session_disconnect(s->session, "closing transport");
199 200
		libssh2_session_free(s->session);
		s->session = NULL;
Brad Morgan committed
201
	}
202

203 204 205 206
	if (s->io) {
		git_stream_close(s->io);
		git_stream_free(s->io);
		s->io = NULL;
Brad Morgan committed
207
	}
208

209
	git_net_url_dispose(&s->url);
Brad Morgan committed
210 211 212
	git__free(s);
}

Brad Morgan committed
213 214 215 216
static int ssh_stream_alloc(
	ssh_subtransport *t,
	const char *cmd,
	git_smart_subtransport_stream **stream)
Brad Morgan committed
217
{
Brad Morgan committed
218
	ssh_stream *s;
219

220
	GIT_ASSERT_ARG(stream);
221

Brad Morgan committed
222
	s = git__calloc(sizeof(ssh_stream), 1);
223
	GIT_ERROR_CHECK_ALLOC(s);
224

Brad Morgan committed
225
	s->parent.subtransport = &t->parent;
Brad Morgan committed
226 227 228
	s->parent.read = ssh_stream_read;
	s->parent.write = ssh_stream_write;
	s->parent.free = ssh_stream_free;
229

Brad Morgan committed
230
	s->cmd = cmd;
231

Brad Morgan committed
232 233 234 235
	*stream = &s->parent;
	return 0;
}

236
static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) {
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
	int rc = LIBSSH2_ERROR_NONE;

	struct libssh2_agent_publickey *curr, *prev = NULL;

	LIBSSH2_AGENT *agent = libssh2_agent_init(session);

	if (agent == NULL)
		return -1;

	rc = libssh2_agent_connect(agent);

	if (rc != LIBSSH2_ERROR_NONE)
		goto shutdown;

	rc = libssh2_agent_list_identities(agent);

	if (rc != LIBSSH2_ERROR_NONE)
		goto shutdown;

	while (1) {
		rc = libssh2_agent_get_identity(agent, &curr, prev);

		if (rc < 0)
			goto shutdown;

262 263 264 265 266 267
		/* rc is set to 1 whenever the ssh agent ran out of keys to check.
		 * Set the error code to authentication failure rather than erroring
		 * out with an untranslatable error code.
		 */
		if (rc == 1) {
			rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
268
			goto shutdown;
269
		}
270 271 272 273 274 275 276 277 278 279

		rc = libssh2_agent_userauth(agent, c->username, curr);

		if (rc == 0)
			break;

		prev = curr;
	}

shutdown:
280 281 282 283

	if (rc != LIBSSH2_ERROR_NONE)
		ssh_error(session, "error authenticating");

284 285 286 287 288 289
	libssh2_agent_disconnect(agent);
	libssh2_agent_free(agent);

	return rc;
}

290
static int _git_ssh_authenticate_session(
291 292
	LIBSSH2_SESSION *session,
	git_credential *cred)
293 294
{
	int rc;
295

296
	do {
297
		git_error_clear();
298
		switch (cred->credtype) {
299 300
		case GIT_CREDENTIAL_USERPASS_PLAINTEXT: {
			git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred;
301
			rc = libssh2_userauth_password(session, c->username, c->password);
302 303
			break;
		}
304 305
		case GIT_CREDENTIAL_SSH_KEY: {
			git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
306 307 308 309 310 311 312 313

			if (c->privatekey)
				rc = libssh2_userauth_publickey_fromfile(
					session, c->username, c->publickey,
					c->privatekey, c->passphrase);
			else
				rc = ssh_agent_auth(session, c);

314 315
			break;
		}
316 317
		case GIT_CREDENTIAL_SSH_CUSTOM: {
			git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred;
318

319
			rc = libssh2_userauth_publickey(
320
				session, c->username, (const unsigned char *)c->publickey,
321
				c->publickey_len, c->sign_callback, &c->payload);
322 323
			break;
		}
324
		case GIT_CREDENTIAL_SSH_INTERACTIVE: {
325
			void **abstract = libssh2_session_abstract(session);
326
			git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred;
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344

			/* ideally, we should be able to set this by calling
			 * libssh2_session_init_ex() instead of libssh2_session_init().
			 * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
			 * allows you to pass the `abstract` as part of the call, whereas
			 * libssh2_userauth_keyboard_interactive() does not!
			 *
			 * The only way to set the `abstract` pointer is by calling
			 * libssh2_session_abstract(), which will replace the existing
			 * pointer as is done below. This is safe for now (at time of writing),
			 * but may not be valid in future.
			 */
			*abstract = c->payload;

			rc = libssh2_userauth_keyboard_interactive(
				session, c->username, c->prompt_callback);
			break;
		}
345
#ifdef GIT_SSH_MEMORY_CREDENTIALS
346 347
		case GIT_CREDENTIAL_SSH_MEMORY: {
			git_credential_ssh_key *c = (git_credential_ssh_key *)cred;
348

349 350
			GIT_ASSERT(c->username);
			GIT_ASSERT(c->privatekey);
351

352 353 354 355 356
			rc = libssh2_userauth_publickey_frommemory(
				session,
				c->username,
				strlen(c->username),
				c->publickey,
357
				c->publickey ? strlen(c->publickey) : 0,
358 359 360 361 362 363
				c->privatekey,
				strlen(c->privatekey),
				c->passphrase);
			break;
		}
#endif
364 365
		default:
			rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
366
		}
Etienne Samson committed
367
	} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
368

369 370 371 372
	if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED ||
		rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED ||
		rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED)
			return GIT_EAUTH;
373

374
	if (rc != LIBSSH2_ERROR_NONE) {
375
		if (!git_error_last())
376
			ssh_error(session, "Failed to authenticate SSH session");
377 378 379 380
		return -1;
	}

	return 0;
381 382
}

383
static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods)
384 385
{
	int error, no_callback = 0;
386
	git_credential *cred = NULL;
387

388
	if (!t->owner->connect_opts.callbacks.credentials) {
389 390
		no_callback = 1;
	} else {
391 392 393 394 395 396
		error = t->owner->connect_opts.callbacks.credentials(
			&cred,
			t->owner->url,
			user,
			auth_methods,
			t->owner->connect_opts.callbacks.payload);
397

398
		if (error == GIT_PASSTHROUGH) {
399
			no_callback = 1;
400
		} else if (error < 0) {
401
			return error;
402
		} else if (!cred) {
403
			git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials");
404 405 406 407 408
			return -1;
		}
	}

	if (no_callback) {
409
		git_error_set(GIT_ERROR_SSH, "authentication required but no callback set");
410
		return GIT_EAUTH;
411 412
	}

413 414
	if (!(cred->credtype & auth_methods)) {
		cred->free(cred);
415 416
		git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type");
		return GIT_EAUTH;
417 418
	}

419 420 421 422 423
	*out = cred;

	return 0;
}

Etienne Samson committed
424
static int _git_ssh_session_create(
425
	LIBSSH2_SESSION **session,
426
	git_stream *io)
Brad Morgan committed
427
{
428
	int rc = 0;
429
	LIBSSH2_SESSION *s;
430
	git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
431

432
	GIT_ASSERT_ARG(session);
433 434 435

	s = libssh2_session_init();
	if (!s) {
436
		git_error_set(GIT_ERROR_NET, "failed to initialize SSH session");
Etienne Samson committed
437
		return -1;
438
	}
439

Etienne Samson committed
440
	do {
441
		rc = libssh2_session_handshake(s, socket->s);
Etienne Samson committed
442
	} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
443

444
	if (rc != LIBSSH2_ERROR_NONE) {
445
		ssh_error(s, "failed to start SSH session");
446 447
		libssh2_session_free(s);
		return -1;
448
	}
449

Brad Morgan committed
450
	libssh2_session_set_blocking(s, 1);
451

Brad Morgan committed
452
	*session = s;
453

Brad Morgan committed
454 455 456
	return 0;
}

457 458
#define SSH_DEFAULT_PORT "22"

Brad Morgan committed
459
static int _git_ssh_setup_conn(
Brad Morgan committed
460 461
	ssh_subtransport *t,
	const char *url,
Brad Morgan committed
462
	const char *cmd,
463
	git_smart_subtransport_stream **stream)
Brad Morgan committed
464
{
465
	int auth_methods, error = 0;
Brad Morgan committed
466
	ssh_stream *s;
467
	git_credential *cred = NULL;
468 469
	LIBSSH2_SESSION *session=NULL;
	LIBSSH2_CHANNEL *channel=NULL;
470

471 472
	t->current_stream = NULL;

Brad Morgan committed
473
	*stream = NULL;
474
	if (ssh_stream_alloc(t, cmd, stream) < 0)
Brad Morgan committed
475
		return -1;
476

Brad Morgan committed
477
	s = (ssh_stream *)*stream;
478 479
	s->session = NULL;
	s->channel = NULL;
480

481 482 483 484
	if (git_net_str_is_url(url))
		error = git_net_url_parse(&s->url, url);
	else
		error = git_net_url_parse_scp(&s->url, url);
485

486
	if (error < 0)
487
		goto done;
488

489
	if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 ||
490
	    (error = git_stream_connect(s->io)) < 0)
491
		goto done;
492

493
	if ((error = _git_ssh_session_create(&session, s->io)) < 0)
494
		goto done;
495

496
	if (t->owner->connect_opts.callbacks.certificate_check != NULL) {
497
		git_cert_hostkey cert = {{ 0 }}, *cert_ptr;
498
		const char *key;
499 500
		size_t cert_len;
		int cert_type;
501

502
		cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2;
503

504 505 506 507 508 509 510 511 512 513 514 515
		key = libssh2_session_hostkey(session, &cert_len, &cert_type);
		if (key != NULL) {
			cert.type |= GIT_CERT_SSH_RAW;
			cert.hostkey = key;
			cert.hostkey_len = cert_len;
			switch (cert_type) {
				case LIBSSH2_HOSTKEY_TYPE_RSA:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA;
					break;
				case LIBSSH2_HOSTKEY_TYPE_DSS:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS;
					break;
516

517
#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
518 519 520 521 522 523 524 525 526
				case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256;
					break;
				case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384;
					break;
				case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521;
					break;
527
#endif
528

529
#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
530 531 532
				case LIBSSH2_HOSTKEY_TYPE_ED25519:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519;
					break;
533
#endif
534 535 536 537 538
				default:
					cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN;
			}
		}

539 540 541 542 543 544 545 546
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256);
		if (key != NULL) {
			cert.type |= GIT_CERT_SSH_SHA256;
			memcpy(&cert.hash_sha256, key, 32);
		}
#endif

547 548
		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
		if (key != NULL) {
549 550
			cert.type |= GIT_CERT_SSH_SHA1;
			memcpy(&cert.hash_sha1, key, 20);
551 552
		}

553 554 555 556 557 558 559
		key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
		if (key != NULL) {
			cert.type |= GIT_CERT_SSH_MD5;
			memcpy(&cert.hash_md5, key, 16);
		}

		if (cert.type == 0) {
560
			git_error_set(GIT_ERROR_SSH, "unable to get the host key");
561 562
			error = -1;
			goto done;
563 564 565
		}

		/* We don't currently trust any hostkeys */
566
		git_error_clear();
567 568 569

		cert_ptr = &cert;

570 571 572
		error = t->owner->connect_opts.callbacks.certificate_check(
			(git_cert *)cert_ptr,
			0,
573
			s->url.host,
574
			t->owner->connect_opts.callbacks.payload);
575 576

		if (error < 0 && error != GIT_PASSTHROUGH) {
577 578
			if (!git_error_last())
				git_error_set(GIT_ERROR_NET, "user cancelled hostkey check");
579

580
			goto done;
581
		}
582
	}
583

584
	/* we need the username to ask for auth methods */
585
	if (!s->url.username) {
586
		if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0)
587
			goto done;
588

589
		s->url.username = git__strdup(((git_credential_username *) cred)->username);
590 591
		cred->free(cred);
		cred = NULL;
592
		if (!s->url.username)
593
			goto done;
594 595
	} else if (s->url.username && s->url.password) {
		if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0)
596
			goto done;
597 598
	}

599
	if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
600
		goto done;
601

602 603
	error = GIT_EAUTH;
	/* if we already have something to try */
604
	if (cred && auth_methods & cred->credtype)
605
		error = _git_ssh_authenticate_session(session, cred);
606

607 608 609 610 611 612
	while (error == GIT_EAUTH) {
		if (cred) {
			cred->free(cred);
			cred = NULL;
		}

613
		if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0)
614
			goto done;
615

616
		if (strcmp(s->url.username, git_credential_get_username(cred))) {
617
			git_error_set(GIT_ERROR_SSH, "username does not match previous request");
618
			error = -1;
619
			goto done;
620 621
		}

622
		error = _git_ssh_authenticate_session(session, cred);
623 624 625

		if (error == GIT_EAUTH) {
			/* refresh auth methods */
626
			if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0)
627
				goto done;
kas committed
628 629
			else
				error = GIT_EAUTH;
630
		}
631
	}
632

633
	if (error < 0)
634
		goto done;
635

Brad Morgan committed
636
	channel = libssh2_channel_open_session(session);
637
	if (!channel) {
638
		error = -1;
639
		ssh_error(session, "Failed to open SSH channel");
640
		goto done;
641
	}
642

Brad Morgan committed
643
	libssh2_channel_set_blocking(channel, 1);
644

Brad Morgan committed
645 646
	s->session = session;
	s->channel = channel;
647

Brad Morgan committed
648
	t->current_stream = s;
649

650 651
done:
	if (error < 0) {
652
		ssh_stream_free(*stream);
653

654 655 656
		if (session)
			libssh2_session_free(session);
	}
657

658 659 660
	if (cred)
		cred->free(cred);

661
	return error;
Brad Morgan committed
662 663
}

Brad Morgan committed
664
static int ssh_uploadpack_ls(
Brad Morgan committed
665 666 667 668
	ssh_subtransport *t,
	const char *url,
	git_smart_subtransport_stream **stream)
{
669 670
	const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;

671
	return _git_ssh_setup_conn(t, url, cmd, stream);
Brad Morgan committed
672 673
}

Brad Morgan committed
674
static int ssh_uploadpack(
Brad Morgan committed
675 676 677 678 679
	ssh_subtransport *t,
	const char *url,
	git_smart_subtransport_stream **stream)
{
	GIT_UNUSED(url);
680

Brad Morgan committed
681 682 683 684
	if (t->current_stream) {
		*stream = &t->current_stream->parent;
		return 0;
	}
685

686
	git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK");
Brad Morgan committed
687 688 689
	return -1;
}

Brad Morgan committed
690
static int ssh_receivepack_ls(
Brad Morgan committed
691 692 693 694
	ssh_subtransport *t,
	const char *url,
	git_smart_subtransport_stream **stream)
{
695 696
	const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;

697

698
	return _git_ssh_setup_conn(t, url, cmd, stream);
Brad Morgan committed
699 700
}

Brad Morgan committed
701
static int ssh_receivepack(
Brad Morgan committed
702 703 704
	ssh_subtransport *t,
	const char *url,
	git_smart_subtransport_stream **stream)
Brad Morgan committed
705 706
{
	GIT_UNUSED(url);
707

Brad Morgan committed
708 709 710 711
	if (t->current_stream) {
		*stream = &t->current_stream->parent;
		return 0;
	}
712

713
	git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK");
Brad Morgan committed
714 715 716
	return -1;
}

Brad Morgan committed
717
static int _ssh_action(
Brad Morgan committed
718 719 720 721
	git_smart_subtransport_stream **stream,
	git_smart_subtransport *subtransport,
	const char *url,
	git_smart_service_t action)
Brad Morgan committed
722
{
723
	ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
724

Brad Morgan committed
725 726
	switch (action) {
		case GIT_SERVICE_UPLOADPACK_LS:
Brad Morgan committed
727
			return ssh_uploadpack_ls(t, url, stream);
728

Brad Morgan committed
729
		case GIT_SERVICE_UPLOADPACK:
Brad Morgan committed
730
			return ssh_uploadpack(t, url, stream);
731

Brad Morgan committed
732
		case GIT_SERVICE_RECEIVEPACK_LS:
Brad Morgan committed
733
			return ssh_receivepack_ls(t, url, stream);
734

Brad Morgan committed
735
		case GIT_SERVICE_RECEIVEPACK:
Brad Morgan committed
736
			return ssh_receivepack(t, url, stream);
Brad Morgan committed
737
	}
738

Brad Morgan committed
739 740 741 742
	*stream = NULL;
	return -1;
}

Brad Morgan committed
743
static int _ssh_close(git_smart_subtransport *subtransport)
Brad Morgan committed
744
{
745
	ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
746

747
	GIT_ASSERT(!t->current_stream);
748

Brad Morgan committed
749
	GIT_UNUSED(t);
750

Brad Morgan committed
751 752 753
	return 0;
}

Brad Morgan committed
754
static void _ssh_free(git_smart_subtransport *subtransport)
Brad Morgan committed
755
{
756
	ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent);
757

758 759
	git__free(t->cmd_uploadpack);
	git__free(t->cmd_receivepack);
Brad Morgan committed
760 761
	git__free(t);
}
762 763 764 765 766

#define SSH_AUTH_PUBLICKEY "publickey"
#define SSH_AUTH_PASSWORD "password"
#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"

767
static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username)
768 769 770 771 772
{
	const char *list, *ptr;

	*out = 0;

773
	list = libssh2_userauth_list(session, username, strlen(username));
774 775

	/* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
776 777
	if (list == NULL && !libssh2_userauth_authenticated(session)) {
		ssh_error(session, "Failed to retrieve list of SSH authentication methods");
778
		return GIT_EAUTH;
779
	}
780 781 782 783 784 785 786

	ptr = list;
	while (ptr) {
		if (*ptr == ',')
			ptr++;

		if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) {
787 788
			*out |= GIT_CREDENTIAL_SSH_KEY;
			*out |= GIT_CREDENTIAL_SSH_CUSTOM;
789
#ifdef GIT_SSH_MEMORY_CREDENTIALS
790
			*out |= GIT_CREDENTIAL_SSH_MEMORY;
791
#endif
792 793 794 795 796
			ptr += strlen(SSH_AUTH_PUBLICKEY);
			continue;
		}

		if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) {
797
			*out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
798 799 800 801 802
			ptr += strlen(SSH_AUTH_PASSWORD);
			continue;
		}

		if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) {
803
			*out |= GIT_CREDENTIAL_SSH_INTERACTIVE;
804 805 806 807
			ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE);
			continue;
		}

Dimitris Apostolou committed
808
		/* Skip it if we don't know it */
809 810 811 812 813
		ptr = strchr(ptr, ',');
	}

	return 0;
}
814
#endif
Brad Morgan committed
815

816
int git_smart_subtransport_ssh(
817
	git_smart_subtransport **out, git_transport *owner, void *param)
Brad Morgan committed
818
{
819
#ifdef GIT_SSH
Brad Morgan committed
820
	ssh_subtransport *t;
821

822
	GIT_ASSERT_ARG(out);
823

824 825
	GIT_UNUSED(param);

Brad Morgan committed
826
	t = git__calloc(sizeof(ssh_subtransport), 1);
827
	GIT_ERROR_CHECK_ALLOC(t);
828

Brad Morgan committed
829
	t->owner = (transport_smart *)owner;
Brad Morgan committed
830 831 832
	t->parent.action = _ssh_action;
	t->parent.close = _ssh_close;
	t->parent.free = _ssh_free;
833

Brad Morgan committed
834 835
	*out = (git_smart_subtransport *) t;
	return 0;
836 837
#else
	GIT_UNUSED(owner);
838
	GIT_UNUSED(param);
839

840
	GIT_ASSERT_ARG(out);
841
	*out = NULL;
842

843
	git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support");
844
	return -1;
845
#endif
846
}
847 848 849 850 851 852 853 854 855 856 857 858

int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
{
#ifdef GIT_SSH
	git_strarray *paths = (git_strarray *) payload;
	git_transport *transport;
	transport_smart *smart;
	ssh_subtransport *t;
	int error;
	git_smart_subtransport_definition ssh_definition = {
		git_smart_subtransport_ssh,
		0, /* no RPC */
859
		NULL,
860 861 862
	};

	if (paths->count != 2) {
863
		git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings");
864 865 866 867 868 869 870 871 872 873
		return GIT_EINVALIDSPEC;
	}

	if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
		return error;

	smart = (transport_smart *) transport;
	t = (ssh_subtransport *) smart->wrapped;

	t->cmd_uploadpack = git__strdup(paths->strings[0]);
874
	GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack);
875
	t->cmd_receivepack = git__strdup(paths->strings[1]);
876
	GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack);
877 878 879 880 881

	*out = transport;
	return 0;
#else
	GIT_UNUSED(owner);
Vicent Marti committed
882
	GIT_UNUSED(payload);
883

884
	GIT_ASSERT_ARG(out);
885 886
	*out = NULL;

887
	git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support");
888 889 890
	return -1;
#endif
}
891

892 893 894 895 896 897 898
#ifdef GIT_SSH
static void shutdown_ssh(void)
{
    libssh2_exit();
}
#endif

899 900 901
int git_transport_ssh_global_init(void)
{
#ifdef GIT_SSH
902
	if (libssh2_init(0) < 0) {
903
		git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2");
904 905
		return -1;
	}
906

907
	return git_runtime_shutdown_register(shutdown_ssh);
908 909 910 911 912 913 914 915

#else

	/* Nothing to initialize */
	return 0;

#endif
}