2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
20 #include "streams/socket.h"
22 #include "git2/credential.h"
23 #include "git2/sys/credential.h"
27 #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
29 static const char *ssh_prefixes
[] = { "ssh://", "ssh+git://", "git+ssh://" };
31 static const char cmd_uploadpack
[] = "git-upload-pack";
32 static const char cmd_receivepack
[] = "git-receive-pack";
35 git_smart_subtransport_stream parent
;
37 LIBSSH2_SESSION
*session
;
38 LIBSSH2_CHANNEL
*channel
;
41 unsigned sent_command
: 1;
45 git_smart_subtransport parent
;
46 transport_smart
*owner
;
47 ssh_stream
*current_stream
;
50 char *cmd_receivepack
;
53 static int list_auth_methods(int *out
, LIBSSH2_SESSION
*session
, const char *username
);
55 static void ssh_error(LIBSSH2_SESSION
*session
, const char *errmsg
)
58 libssh2_session_last_error(session
, &ssherr
, NULL
, 0);
60 git_error_set(GIT_ERROR_SSH
, "%s: %s", errmsg
, ssherr
);
64 * Create a git protocol request.
66 * For example: git-upload-pack '/libgit2/libgit2'
68 static int gen_proto(git_buf
*request
, const char *cmd
, const char *url
)
74 for (i
= 0; i
< ARRAY_SIZE(ssh_prefixes
); ++i
) {
75 const char *p
= ssh_prefixes
[i
];
77 if (!git__prefixcmp(url
, p
)) {
78 url
= url
+ strlen(p
);
79 repo
= strchr(url
, '/');
80 if (repo
&& repo
[1] == '~')
86 repo
= strchr(url
, ':');
91 git_error_set(GIT_ERROR_NET
, "malformed git protocol URL");
95 len
= strlen(cmd
) + 1 /* Space */ + 1 /* Quote */ + strlen(repo
) + 1 /* Quote */ + 1;
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
, "'");
103 if (git_buf_oom(request
))
109 static int send_command(ssh_stream
*s
)
112 git_buf request
= GIT_BUF_INIT
;
114 error
= gen_proto(&request
, s
->cmd
, s
->url
);
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");
127 git_buf_dispose(&request
);
131 static int ssh_stream_read(
132 git_smart_subtransport_stream
*stream
,
138 ssh_stream
*s
= GIT_CONTAINER_OF(stream
, ssh_stream
, parent
);
142 if (!s
->sent_command
&& send_command(s
) < 0)
145 if ((rc
= libssh2_channel_read(s
->channel
, buffer
, buf_size
)) < LIBSSH2_ERROR_NONE
) {
146 ssh_error(s
->session
, "SSH could not read data");
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
156 if ((rc
= libssh2_channel_read_stderr(s
->channel
, buffer
, buf_size
)) > 0) {
157 git_error_set(GIT_ERROR_SSH
, "%*s", rc
, buffer
);
159 } else if (rc
< LIBSSH2_ERROR_NONE
) {
160 ssh_error(s
->session
, "SSH could not read stderr");
171 static int ssh_stream_write(
172 git_smart_subtransport_stream
*stream
,
176 ssh_stream
*s
= GIT_CONTAINER_OF(stream
, ssh_stream
, parent
);
180 if (!s
->sent_command
&& send_command(s
) < 0)
184 ret
= libssh2_channel_write(s
->channel
, buffer
+ off
, len
- off
);
193 ssh_error(s
->session
, "SSH could not write data");
200 static void ssh_stream_free(git_smart_subtransport_stream
*stream
)
202 ssh_stream
*s
= GIT_CONTAINER_OF(stream
, ssh_stream
, parent
);
208 t
= OWNING_SUBTRANSPORT(s
);
209 t
->current_stream
= NULL
;
212 libssh2_channel_close(s
->channel
);
213 libssh2_channel_free(s
->channel
);
218 libssh2_session_disconnect(s
->session
, "closing transport");
219 libssh2_session_free(s
->session
);
224 git_stream_close(s
->io
);
225 git_stream_free(s
->io
);
233 static int ssh_stream_alloc(
237 git_smart_subtransport_stream
**stream
)
241 GIT_ASSERT_ARG(stream
);
243 s
= git__calloc(sizeof(ssh_stream
), 1);
244 GIT_ERROR_CHECK_ALLOC(s
);
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
;
253 s
->url
= git__strdup(url
);
259 *stream
= &s
->parent
;
263 static int git_ssh_extract_url_parts(
264 git_net_url
*urldata
,
270 colon
= strchr(url
, ':');
273 at
= strchr(url
, '@');
276 urldata
->username
= git__substrdup(url
, at
- url
);
277 GIT_ERROR_CHECK_ALLOC(urldata
->username
);
280 urldata
->username
= NULL
;
283 if (colon
== NULL
|| (colon
< start
)) {
284 git_error_set(GIT_ERROR_NET
, "malformed URL");
288 urldata
->host
= git__substrdup(start
, colon
- start
);
289 GIT_ERROR_CHECK_ALLOC(urldata
->host
);
294 static int ssh_agent_auth(LIBSSH2_SESSION
*session
, git_credential_ssh_key
*c
) {
295 int rc
= LIBSSH2_ERROR_NONE
;
297 struct libssh2_agent_publickey
*curr
, *prev
= NULL
;
299 LIBSSH2_AGENT
*agent
= libssh2_agent_init(session
);
304 rc
= libssh2_agent_connect(agent
);
306 if (rc
!= LIBSSH2_ERROR_NONE
)
309 rc
= libssh2_agent_list_identities(agent
);
311 if (rc
!= LIBSSH2_ERROR_NONE
)
315 rc
= libssh2_agent_get_identity(agent
, &curr
, prev
);
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.
325 rc
= LIBSSH2_ERROR_AUTHENTICATION_FAILED
;
329 rc
= libssh2_agent_userauth(agent
, c
->username
, curr
);
339 if (rc
!= LIBSSH2_ERROR_NONE
)
340 ssh_error(session
, "error authenticating");
342 libssh2_agent_disconnect(agent
);
343 libssh2_agent_free(agent
);
348 static int _git_ssh_authenticate_session(
349 LIBSSH2_SESSION
*session
,
350 git_credential
*cred
)
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
);
362 case GIT_CREDENTIAL_SSH_KEY
: {
363 git_credential_ssh_key
*c
= (git_credential_ssh_key
*)cred
;
366 rc
= libssh2_userauth_publickey_fromfile(
367 session
, c
->username
, c
->publickey
,
368 c
->privatekey
, c
->passphrase
);
370 rc
= ssh_agent_auth(session
, c
);
374 case GIT_CREDENTIAL_SSH_CUSTOM
: {
375 git_credential_ssh_custom
*c
= (git_credential_ssh_custom
*)cred
;
377 rc
= libssh2_userauth_publickey(
378 session
, c
->username
, (const unsigned char *)c
->publickey
,
379 c
->publickey_len
, c
->sign_callback
, &c
->payload
);
382 case GIT_CREDENTIAL_SSH_INTERACTIVE
: {
383 void **abstract
= libssh2_session_abstract(session
);
384 git_credential_ssh_interactive
*c
= (git_credential_ssh_interactive
*)cred
;
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!
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.
397 *abstract
= c
->payload
;
399 rc
= libssh2_userauth_keyboard_interactive(
400 session
, c
->username
, c
->prompt_callback
);
403 #ifdef GIT_SSH_MEMORY_CREDENTIALS
404 case GIT_CREDENTIAL_SSH_MEMORY
: {
405 git_credential_ssh_key
*c
= (git_credential_ssh_key
*)cred
;
407 GIT_ASSERT(c
->username
);
408 GIT_ASSERT(c
->privatekey
);
410 rc
= libssh2_userauth_publickey_frommemory(
415 c
->publickey
? strlen(c
->publickey
) : 0,
417 strlen(c
->privatekey
),
423 rc
= LIBSSH2_ERROR_AUTHENTICATION_FAILED
;
425 } while (LIBSSH2_ERROR_EAGAIN
== rc
|| LIBSSH2_ERROR_TIMEOUT
== rc
);
427 if (rc
== LIBSSH2_ERROR_PASSWORD_EXPIRED
||
428 rc
== LIBSSH2_ERROR_AUTHENTICATION_FAILED
||
429 rc
== LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED
)
432 if (rc
!= LIBSSH2_ERROR_NONE
) {
433 if (!git_error_last())
434 ssh_error(session
, "Failed to authenticate SSH session");
441 static int request_creds(git_credential
**out
, ssh_subtransport
*t
, const char *user
, int auth_methods
)
443 int error
, no_callback
= 0;
444 git_credential
*cred
= NULL
;
446 if (!t
->owner
->cred_acquire_cb
) {
449 error
= t
->owner
->cred_acquire_cb(&cred
, t
->owner
->url
, user
, auth_methods
,
450 t
->owner
->cred_acquire_payload
);
452 if (error
== GIT_PASSTHROUGH
) {
454 } else if (error
< 0) {
457 git_error_set(GIT_ERROR_SSH
, "callback failed to initialize SSH credentials");
463 git_error_set(GIT_ERROR_SSH
, "authentication required but no callback set");
467 if (!(cred
->credtype
& auth_methods
)) {
469 git_error_set(GIT_ERROR_SSH
, "authentication callback returned unsupported credentials type");
478 static int _git_ssh_session_create(
479 LIBSSH2_SESSION
**session
,
484 git_socket_stream
*socket
= GIT_CONTAINER_OF(io
, git_socket_stream
, parent
);
486 GIT_ASSERT_ARG(session
);
488 s
= libssh2_session_init();
490 git_error_set(GIT_ERROR_NET
, "failed to initialize SSH session");
495 rc
= libssh2_session_handshake(s
, socket
->s
);
496 } while (LIBSSH2_ERROR_EAGAIN
== rc
|| LIBSSH2_ERROR_TIMEOUT
== rc
);
498 if (rc
!= LIBSSH2_ERROR_NONE
) {
499 ssh_error(s
, "failed to start SSH session");
500 libssh2_session_free(s
);
504 libssh2_session_set_blocking(s
, 1);
511 #define SSH_DEFAULT_PORT "22"
513 static int _git_ssh_setup_conn(
517 git_smart_subtransport_stream
**stream
)
519 git_net_url urldata
= GIT_NET_URL_INIT
;
520 int auth_methods
, error
= 0;
523 git_credential
*cred
= NULL
;
524 LIBSSH2_SESSION
*session
=NULL
;
525 LIBSSH2_CHANNEL
*channel
=NULL
;
527 t
->current_stream
= NULL
;
530 if (ssh_stream_alloc(t
, url
, cmd
, stream
) < 0)
533 s
= (ssh_stream
*)*stream
;
537 for (i
= 0; i
< ARRAY_SIZE(ssh_prefixes
); ++i
) {
538 const char *p
= ssh_prefixes
[i
];
540 if (!git__prefixcmp(url
, p
)) {
541 if ((error
= git_net_url_parse(&urldata
, url
)) < 0)
547 if ((error
= git_ssh_extract_url_parts(&urldata
, url
)) < 0)
550 if (urldata
.port
== NULL
)
551 urldata
.port
= git__strdup(SSH_DEFAULT_PORT
);
553 GIT_ERROR_CHECK_ALLOC(urldata
.port
);
556 if ((error
= git_socket_stream_new(&s
->io
, urldata
.host
, urldata
.port
)) < 0 ||
557 (error
= git_stream_connect(s
->io
)) < 0)
560 if ((error
= _git_ssh_session_create(&session
, s
->io
)) < 0)
563 if (t
->owner
->certificate_check_cb
!= NULL
) {
564 git_cert_hostkey cert
= {{ 0 }}, *cert_ptr
;
569 cert
.parent
.cert_type
= GIT_CERT_HOSTKEY_LIBSSH2
;
571 key
= libssh2_session_hostkey(session
, &cert_len
, &cert_type
);
573 cert
.type
|= GIT_CERT_SSH_RAW
;
575 cert
.hostkey_len
= cert_len
;
577 case LIBSSH2_HOSTKEY_TYPE_RSA
:
578 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_RSA
;
580 case LIBSSH2_HOSTKEY_TYPE_DSS
:
581 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_DSS
;
584 #ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
585 case LIBSSH2_HOSTKEY_TYPE_ECDSA_256
:
586 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256
;
588 case LIBSSH2_HOSTKEY_TYPE_ECDSA_384
:
589 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384
;
591 case LIBSSH2_KNOWNHOST_KEY_ECDSA_521
:
592 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521
;
596 #ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
597 case LIBSSH2_HOSTKEY_TYPE_ED25519
:
598 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_KEY_ED25519
;
602 cert
.raw_type
= GIT_CERT_SSH_RAW_TYPE_UNKNOWN
;
606 #ifdef LIBSSH2_HOSTKEY_HASH_SHA256
607 key
= libssh2_hostkey_hash(session
, LIBSSH2_HOSTKEY_HASH_SHA256
);
609 cert
.type
|= GIT_CERT_SSH_SHA256
;
610 memcpy(&cert
.hash_sha256
, key
, 32);
614 key
= libssh2_hostkey_hash(session
, LIBSSH2_HOSTKEY_HASH_SHA1
);
616 cert
.type
|= GIT_CERT_SSH_SHA1
;
617 memcpy(&cert
.hash_sha1
, key
, 20);
620 key
= libssh2_hostkey_hash(session
, LIBSSH2_HOSTKEY_HASH_MD5
);
622 cert
.type
|= GIT_CERT_SSH_MD5
;
623 memcpy(&cert
.hash_md5
, key
, 16);
626 if (cert
.type
== 0) {
627 git_error_set(GIT_ERROR_SSH
, "unable to get the host key");
632 /* We don't currently trust any hostkeys */
637 error
= t
->owner
->certificate_check_cb((git_cert
*) cert_ptr
, 0, urldata
.host
, t
->owner
->message_cb_payload
);
639 if (error
< 0 && error
!= GIT_PASSTHROUGH
) {
640 if (!git_error_last())
641 git_error_set(GIT_ERROR_NET
, "user cancelled hostkey check");
647 /* we need the username to ask for auth methods */
648 if (!urldata
.username
) {
649 if ((error
= request_creds(&cred
, t
, NULL
, GIT_CREDENTIAL_USERNAME
)) < 0)
652 urldata
.username
= git__strdup(((git_credential_username
*) cred
)->username
);
655 if (!urldata
.username
)
657 } else if (urldata
.username
&& urldata
.password
) {
658 if ((error
= git_credential_userpass_plaintext_new(&cred
, urldata
.username
, urldata
.password
)) < 0)
662 if ((error
= list_auth_methods(&auth_methods
, session
, urldata
.username
)) < 0)
666 /* if we already have something to try */
667 if (cred
&& auth_methods
& cred
->credtype
)
668 error
= _git_ssh_authenticate_session(session
, cred
);
670 while (error
== GIT_EAUTH
) {
676 if ((error
= request_creds(&cred
, t
, urldata
.username
, auth_methods
)) < 0)
679 if (strcmp(urldata
.username
, git_credential_get_username(cred
))) {
680 git_error_set(GIT_ERROR_SSH
, "username does not match previous request");
685 error
= _git_ssh_authenticate_session(session
, cred
);
687 if (error
== GIT_EAUTH
) {
688 /* refresh auth methods */
689 if ((error
= list_auth_methods(&auth_methods
, session
, urldata
.username
)) < 0)
699 channel
= libssh2_channel_open_session(session
);
702 ssh_error(session
, "Failed to open SSH channel");
706 libssh2_channel_set_blocking(channel
, 1);
708 s
->session
= session
;
709 s
->channel
= channel
;
711 t
->current_stream
= s
;
715 ssh_stream_free(*stream
);
718 libssh2_session_free(session
);
724 git_net_url_dispose(&urldata
);
729 static int ssh_uploadpack_ls(
732 git_smart_subtransport_stream
**stream
)
734 const char *cmd
= t
->cmd_uploadpack
? t
->cmd_uploadpack
: cmd_uploadpack
;
736 return _git_ssh_setup_conn(t
, url
, cmd
, stream
);
739 static int ssh_uploadpack(
742 git_smart_subtransport_stream
**stream
)
746 if (t
->current_stream
) {
747 *stream
= &t
->current_stream
->parent
;
751 git_error_set(GIT_ERROR_NET
, "must call UPLOADPACK_LS before UPLOADPACK");
755 static int ssh_receivepack_ls(
758 git_smart_subtransport_stream
**stream
)
760 const char *cmd
= t
->cmd_receivepack
? t
->cmd_receivepack
: cmd_receivepack
;
763 return _git_ssh_setup_conn(t
, url
, cmd
, stream
);
766 static int ssh_receivepack(
769 git_smart_subtransport_stream
**stream
)
773 if (t
->current_stream
) {
774 *stream
= &t
->current_stream
->parent
;
778 git_error_set(GIT_ERROR_NET
, "must call RECEIVEPACK_LS before RECEIVEPACK");
782 static int _ssh_action(
783 git_smart_subtransport_stream
**stream
,
784 git_smart_subtransport
*subtransport
,
786 git_smart_service_t action
)
788 ssh_subtransport
*t
= GIT_CONTAINER_OF(subtransport
, ssh_subtransport
, parent
);
791 case GIT_SERVICE_UPLOADPACK_LS
:
792 return ssh_uploadpack_ls(t
, url
, stream
);
794 case GIT_SERVICE_UPLOADPACK
:
795 return ssh_uploadpack(t
, url
, stream
);
797 case GIT_SERVICE_RECEIVEPACK_LS
:
798 return ssh_receivepack_ls(t
, url
, stream
);
800 case GIT_SERVICE_RECEIVEPACK
:
801 return ssh_receivepack(t
, url
, stream
);
808 static int _ssh_close(git_smart_subtransport
*subtransport
)
810 ssh_subtransport
*t
= GIT_CONTAINER_OF(subtransport
, ssh_subtransport
, parent
);
812 GIT_ASSERT(!t
->current_stream
);
819 static void _ssh_free(git_smart_subtransport
*subtransport
)
821 ssh_subtransport
*t
= GIT_CONTAINER_OF(subtransport
, ssh_subtransport
, parent
);
823 git__free(t
->cmd_uploadpack
);
824 git__free(t
->cmd_receivepack
);
828 #define SSH_AUTH_PUBLICKEY "publickey"
829 #define SSH_AUTH_PASSWORD "password"
830 #define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive"
832 static int list_auth_methods(int *out
, LIBSSH2_SESSION
*session
, const char *username
)
834 const char *list
, *ptr
;
838 list
= libssh2_userauth_list(session
, username
, strlen(username
));
840 /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
841 if (list
== NULL
&& !libssh2_userauth_authenticated(session
)) {
842 ssh_error(session
, "Failed to retrieve list of SSH authentication methods");
851 if (!git__prefixcmp(ptr
, SSH_AUTH_PUBLICKEY
)) {
852 *out
|= GIT_CREDENTIAL_SSH_KEY
;
853 *out
|= GIT_CREDENTIAL_SSH_CUSTOM
;
854 #ifdef GIT_SSH_MEMORY_CREDENTIALS
855 *out
|= GIT_CREDENTIAL_SSH_MEMORY
;
857 ptr
+= strlen(SSH_AUTH_PUBLICKEY
);
861 if (!git__prefixcmp(ptr
, SSH_AUTH_PASSWORD
)) {
862 *out
|= GIT_CREDENTIAL_USERPASS_PLAINTEXT
;
863 ptr
+= strlen(SSH_AUTH_PASSWORD
);
867 if (!git__prefixcmp(ptr
, SSH_AUTH_KEYBOARD_INTERACTIVE
)) {
868 *out
|= GIT_CREDENTIAL_SSH_INTERACTIVE
;
869 ptr
+= strlen(SSH_AUTH_KEYBOARD_INTERACTIVE
);
873 /* Skipt it if we don't know it */
874 ptr
= strchr(ptr
, ',');
881 int git_smart_subtransport_ssh(
882 git_smart_subtransport
**out
, git_transport
*owner
, void *param
)
891 t
= git__calloc(sizeof(ssh_subtransport
), 1);
892 GIT_ERROR_CHECK_ALLOC(t
);
894 t
->owner
= (transport_smart
*)owner
;
895 t
->parent
.action
= _ssh_action
;
896 t
->parent
.close
= _ssh_close
;
897 t
->parent
.free
= _ssh_free
;
899 *out
= (git_smart_subtransport
*) t
;
908 git_error_set(GIT_ERROR_INVALID
, "cannot create SSH transport. Library was built without SSH support");
913 int git_transport_ssh_with_paths(git_transport
**out
, git_remote
*owner
, void *payload
)
916 git_strarray
*paths
= (git_strarray
*) payload
;
917 git_transport
*transport
;
918 transport_smart
*smart
;
921 git_smart_subtransport_definition ssh_definition
= {
922 git_smart_subtransport_ssh
,
927 if (paths
->count
!= 2) {
928 git_error_set(GIT_ERROR_SSH
, "invalid ssh paths, must be two strings");
929 return GIT_EINVALIDSPEC
;
932 if ((error
= git_transport_smart(&transport
, owner
, &ssh_definition
)) < 0)
935 smart
= (transport_smart
*) transport
;
936 t
= (ssh_subtransport
*) smart
->wrapped
;
938 t
->cmd_uploadpack
= git__strdup(paths
->strings
[0]);
939 GIT_ERROR_CHECK_ALLOC(t
->cmd_uploadpack
);
940 t
->cmd_receivepack
= git__strdup(paths
->strings
[1]);
941 GIT_ERROR_CHECK_ALLOC(t
->cmd_receivepack
);
952 git_error_set(GIT_ERROR_INVALID
, "cannot create SSH transport. Library was built without SSH support");
958 static void shutdown_ssh(void)
964 int git_transport_ssh_global_init(void)
967 if (libssh2_init(0) < 0) {
968 git_error_set(GIT_ERROR_SSH
, "unable to initialize libssh2");
972 return git_runtime_shutdown_register(shutdown_ssh
);
976 /* Nothing to initialize */