summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Martí <vicent@github.com>2013-05-16 11:35:02 -0700
committerVicent Martí <vicent@github.com>2013-05-16 11:35:02 -0700
commite3107e0ee182403f141178ee323e66c516cf3f08 (patch)
tree3ad700e9b54fdd4a9d15d104e7d74dbf0f8ce050 /src
parentc2d282cfd88ffba7693113786bd2209a5d38b964 (diff)
parent84ac625ddd1135755030dfb01b2be965e7cacb2f (diff)
downloadlibgit2-e3107e0ee182403f141178ee323e66c516cf3f08.tar.gz
Merge pull request #1558 from bmorganpa/ssh_transport
SSH Transport
Diffstat (limited to 'src')
-rw-r--r--src/transport.c24
-rw-r--r--src/transports/cred.c102
-rw-r--r--src/transports/ssh.c519
3 files changed, 639 insertions, 6 deletions
diff --git a/src/transport.c b/src/transport.c
index adb6d5355..37c244c97 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -18,19 +18,27 @@ typedef struct transport_definition {
void *param;
} transport_definition;
-static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
-static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
-
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
+#ifdef GIT_SSH
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
+#endif
+
+static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
+#ifdef GIT_SSH
+static transport_definition ssh_transport_definition = { "ssh://", 1, git_transport_smart, &ssh_subtransport_definition };
+#else
+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
+#endif
static transport_definition transports[] = {
{"git://", 1, git_transport_smart, &git_subtransport_definition},
{"http://", 1, git_transport_smart, &http_subtransport_definition},
{"https://", 1, git_transport_smart, &http_subtransport_definition},
{"file://", 1, git_transport_local, NULL},
- {"git+ssh://", 1, git_transport_dummy, NULL},
- {"ssh+git://", 1, git_transport_dummy, NULL},
+#ifdef GIT_SSH
+ {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition},
+#endif
{NULL, 0, 0}
};
@@ -73,7 +81,11 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
/* It could be a SSH remote path. Check to see if there's a :
* SSH is an unsupported transport mechanism in this version of libgit2 */
if (!definition && strrchr(url, ':'))
- definition = &dummy_transport_definition;
+#ifdef GIT_SSH
+ definition = &ssh_transport_definition;
+#else
+ definition = &dummy_transport_definition;
+#endif
/* Check to see if the path points to a file on the local file system */
if (!definition && git_path_exists(url) && git_path_isdir(url))
diff --git a/src/transports/cred.c b/src/transports/cred.c
index ecb026062..4916c6e18 100644
--- a/src/transports/cred.c
+++ b/src/transports/cred.c
@@ -58,3 +58,105 @@ int git_cred_userpass_plaintext_new(
*cred = &c->parent;
return 0;
}
+
+#ifdef GIT_SSH
+static void ssh_keyfile_passphrase_free(struct git_cred *cred)
+{
+ git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred;
+ size_t pass_len = strlen(c->passphrase);
+
+ if (c->publickey) {
+ git__free(c->publickey);
+ }
+
+ git__free(c->privatekey);
+
+ if (c->passphrase) {
+ /* Zero the memory which previously held the passphrase */
+ memset(c->passphrase, 0x0, pass_len);
+ git__free(c->passphrase);
+ }
+
+ memset(c, 0, sizeof(*c));
+
+ git__free(c);
+}
+
+int git_cred_ssh_keyfile_passphrase_new(
+ git_cred **cred,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+ git_cred_ssh_keyfile_passphrase *c;
+
+ assert(cred && privatekey);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_keyfile_passphrase));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE;
+ c->parent.free = ssh_keyfile_passphrase_free;
+
+ c->privatekey = git__strdup(privatekey);
+ GITERR_CHECK_ALLOC(c->privatekey);
+
+ if (publickey) {
+ c->publickey = git__strdup(publickey);
+ GITERR_CHECK_ALLOC(c->publickey);
+ }
+
+ if (passphrase) {
+ c->passphrase = git__strdup(passphrase);
+ GITERR_CHECK_ALLOC(c->passphrase);
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+static void ssh_publickey_free(struct git_cred *cred)
+{
+ git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred;
+
+ git__free(c->publickey);
+
+ c->sign_callback = NULL;
+ c->sign_data = NULL;
+
+ memset(c, 0, sizeof(*c));
+
+ git__free(c);
+}
+
+int git_cred_ssh_publickey_new(
+ git_cred **cred,
+ const char *publickey,
+ size_t publickey_len,
+ LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+ void *sign_data)
+{
+ git_cred_ssh_publickey *c;
+
+ if (!cred)
+ return -1;
+
+ c = git__malloc(sizeof(git_cred_ssh_publickey));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_PUBLICKEY;
+ c->parent.free = ssh_publickey_free;
+
+ c->publickey = git__malloc(publickey_len);
+ GITERR_CHECK_ALLOC(c->publickey);
+
+ memcpy(c->publickey, publickey, publickey_len);
+
+ c->publickey_len = publickey_len;
+ c->sign_callback = sign_callback;
+ c->sign_data = sign_data;
+
+ *cred = &c->parent;
+ return 0;
+}
+#endif
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
new file mode 100644
index 000000000..a312c8d08
--- /dev/null
+++ b/src/transports/ssh.c
@@ -0,0 +1,519 @@
+/*
+ * 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.
+ */
+
+#ifdef GIT_SSH
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+#include "smart.h"
+
+#include <libssh2.h>
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_ssh[] = "ssh://";
+static const char default_user[] = "git";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ ssh_stream *current_stream;
+ git_cred *cred;
+} ssh_subtransport;
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: git-upload-pack '/libgit2/libgit2'
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *repo;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ url = url + strlen(prefix_ssh);
+ repo = strchr(url, '/');
+ } else {
+ repo = strchr(url, ':');
+ }
+
+ if (!repo) {
+ return -1;
+ }
+
+ int len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%s '%s'", cmd, repo);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = libssh2_channel_exec(
+ s->channel,
+ request.ptr
+ );
+
+ if (0 != error)
+ goto cleanup;
+
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ int rc = libssh2_channel_read(s->channel, buffer, buf_size);
+ if (rc < 0)
+ return -1;
+
+ *bytes_read = rc;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ int rc = libssh2_channel_write(s->channel, buffer, len);
+ if (rc < 0) {
+ return -1;
+ }
+
+ return rc;
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->channel) {
+ libssh2_channel_close(s->channel);
+ libssh2_channel_free(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->session) {
+ libssh2_session_free(s->session), s->session = NULL;
+ }
+
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ ssh_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(ssh_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = ssh_stream_read;
+ s->parent.write = ssh_stream_write;
+ s->parent.free = ssh_stream_free;
+
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int git_ssh_extract_url_parts(
+ char **host,
+ char **username,
+ const char *url)
+{
+ char *colon, *at;
+ const char *start;
+
+ colon = strchr(url, ':');
+
+ if (colon == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL: missing :");
+ return -1;
+ }
+
+ at = strchr(url, '@');
+ if (at) {
+ start = at+1;
+ *username = git__substrdup(url, at - url);
+ } else {
+ start = url;
+ *username = git__strdup(default_user);
+ }
+
+ *host = git__substrdup(start, colon - start);
+
+ return 0;
+}
+
+static int _git_ssh_authenticate_session(
+ LIBSSH2_SESSION* session,
+ const char *user,
+ git_cred* cred
+)
+{
+ int rc;
+ do {
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ rc = libssh2_userauth_password(
+ session,
+ c->username,
+ c->password
+ );
+ break;
+ }
+ case GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE: {
+ git_cred_ssh_keyfile_passphrase *c = (git_cred_ssh_keyfile_passphrase *)cred;
+ rc = libssh2_userauth_publickey_fromfile(
+ session,
+ user,
+ c->publickey,
+ c->privatekey,
+ c->passphrase
+ );
+ break;
+ }
+ case GIT_CREDTYPE_SSH_PUBLICKEY: {
+ git_cred_ssh_publickey *c = (git_cred_ssh_publickey *)cred;
+ rc = libssh2_userauth_publickey(
+ session,
+ user,
+ (const unsigned char *)c->publickey,
+ c->publickey_len,
+ c->sign_callback,
+ &c->sign_data
+ );
+ break;
+ }
+ default:
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ }
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ return rc;
+}
+
+static int _git_ssh_session_create
+(
+ LIBSSH2_SESSION** session,
+ gitno_socket socket
+)
+{
+ if (!session) {
+ return -1;
+ }
+
+ LIBSSH2_SESSION* s = libssh2_session_init();
+ if (!s)
+ return -1;
+
+ int rc = 0;
+ do {
+ rc = libssh2_session_startup(s, socket.socket);
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (0 != rc) {
+ goto on_error;
+ }
+
+ libssh2_session_set_blocking(s, 1);
+
+ *session = s;
+
+ return 0;
+
+on_error:
+ if (s) {
+ libssh2_session_free(s), s = NULL;
+ }
+
+ return -1;
+}
+
+static int _git_ssh_setup_conn(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream
+)
+{
+ char *host, *port=NULL, *user=NULL, *pass=NULL;
+ const char *default_port="22";
+ ssh_stream *s;
+ LIBSSH2_SESSION* session=NULL;
+ LIBSSH2_CHANNEL* channel=NULL;
+
+ *stream = NULL;
+ if (ssh_stream_alloc(t, url, cmd, stream) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ url = url + strlen(prefix_ssh);
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, default_port) < 0)
+ goto on_error;
+ } else {
+ if (git_ssh_extract_url_parts(&host, &user, url) < 0)
+ goto on_error;
+ port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(port);
+ }
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ if (user && pass) {
+ if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
+ goto on_error;
+ } else {
+ if (t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ user,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT | GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE,
+ t->owner->cred_acquire_payload) < 0)
+ return -1;
+ }
+ assert(t->cred);
+
+ if (!user) {
+ user = git__strdup(default_user);
+ }
+
+ if (_git_ssh_session_create(&session, s->socket) < 0)
+ goto on_error;
+
+ if (_git_ssh_authenticate_session(session, user, t->cred) < 0)
+ goto on_error;
+
+ channel = libssh2_channel_open_session(session);
+ if (!channel)
+ goto on_error;
+
+ libssh2_channel_set_blocking(channel, 1);
+
+ s->session = session;
+ s->channel = channel;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+
+ return 0;
+
+on_error:
+ if (*stream)
+ ssh_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+
+ if (session)
+ libssh2_session_free(session), session = NULL;
+
+ return -1;
+}
+
+static int ssh_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int ssh_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _ssh_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return ssh_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return ssh_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _ssh_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _ssh_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner)
+{
+ ssh_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = _ssh_action;
+ t->parent.close = _ssh_close;
+ t->parent.free = _ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif