Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

transports: add SSH subtransport - WIP #1103

Closed
wants to merge 1 commit into from

4 participants

Michael Schubert Sascha Cunz Tracey Eubanks Tom Hunter
Michael Schubert
Collaborator

No description provided.

Michael Schubert schu transports: add SSH subtransport
Add a libssh2 based SSH subtransport module.
5b46eec
Sascha Cunz

There is a more advanced cmake find script for libSSH / openSSH available. It's BSD-Licensed, but as we would be "just" using it, I don't see a problem with that.

This however would also search for correct include directories, which you are missing here.

Michael Schubert
Collaborator

Thanks for the pointer.

Tracey Eubanks

What's the status of this? Is it still in process?

Michael Schubert
Collaborator

It rather became stale since and I didn't continue working on it yet. Hope to do so soonish, though..

Tom Hunter

+1 vote from me. This would be very useful for SourceLog - https://github.com/tomhunter-gh/SourceLog

Michael Schubert
Collaborator

Closing in favor of #1558.

Michael Schubert schu closed this
Maxim Shekhovets maxymshg referenced this pull request in tomhunter-gh/SourceLog
Open

Git plugin - SSH Transport support #31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 25, 2012
  1. Michael Schubert

    transports: add SSH subtransport

    schu authored
    Add a libssh2 based SSH subtransport module.
This page is out of date. Refresh to see the latest.
2  .gitignore
View
@@ -23,8 +23,6 @@ msvc/Release/
*.sdf
*.opensdf
*.aps
-CMake*
-*.cmake
.DS_Store
*~
tags
10 CMakeLists.txt
View
@@ -14,6 +14,8 @@
PROJECT(libgit2 C)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+SET(CMAKE_PREFIX_PATH "cmake/")
+
FILE(STRINGS "include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$")
STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${GIT2_HEADER}")
@@ -132,6 +134,12 @@ IF (OPENSSL_FOUND)
SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES})
ENDIF()
+FIND_PACKAGE(LIBSSH2)
+IF (LIBSSH2_FOUND)
+ ADD_DEFINITIONS(-DGIT_SSH)
+ SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
+ENDIF()
+
IF (THREADSAFE)
IF (NOT WIN32)
find_package(Threads REQUIRED)
@@ -168,6 +176,7 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
ENDIF ()
TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${SSH_LIBRARIES})
SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR})
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
@@ -210,6 +219,7 @@ IF (BUILD_CLAR)
)
ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1})
TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSH_LIBRARIES})
IF (MSVC_IDE)
# Precompiled headers
4 cmake/libssh2-config.cmake
View
@@ -0,0 +1,4 @@
+
+FIND_LIBRARY(LIBSSH2_LIBRARY NAMES ssh2 libssh2)
+
+SET(LIBSSH2_LIBRARIES ${LIBSSH2_LIBRARY})
24 include/git2/transport.h
View
@@ -26,6 +26,7 @@ GIT_BEGIN_DECL
typedef enum {
/* git_cred_userpass_plaintext */
GIT_CREDTYPE_USERPASS_PLAINTEXT = 1,
+ GIT_CREDTYPE_SSH_PASSWORD = 2,
} git_credtype_t;
/* The base structure for all credential types */
@@ -42,6 +43,12 @@ typedef struct git_cred_userpass_plaintext {
char *password;
} git_cred_userpass_plaintext;
+typedef struct git_cred_ssh_password {
+ git_cred parent;
+ char *username;
+ char *password;
+} git_cred_ssh_password;
+
/**
* Creates a new plain-text username and password credential object.
*
@@ -54,6 +61,12 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
const char *username,
const char *password);
+GIT_EXTERN(int) git_cred_ssh_password_new(
+ git_cred **cred,
+ /* TODO: known_hosts */
+ const char *username,
+ const char *password);
+
/**
* Signature of a function which acquires a credential object.
*
@@ -297,6 +310,17 @@ GIT_EXTERN(int) git_smart_subtransport_git(
git_smart_subtransport **out,
git_transport* owner);
+/**
+ * Create an instance of the ssh subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_ssh(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
/*
*** End interface for subtransports for the smart transport ***
*/
4 src/transport.c
View
@@ -23,14 +23,14 @@ static transport_definition dummy_transport_definition = { NULL, 1, git_transpor
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 };
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
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},
+ {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition},
{"file://", 1, git_transport_local, NULL},
- {"git+ssh://", 1, git_transport_dummy, NULL},
- {"ssh+git://", 1, git_transport_dummy, NULL},
{NULL, 0, 0}
};
50 src/transports/cred.c
View
@@ -54,4 +54,52 @@ int git_cred_userpass_plaintext_new(
*cred = &c->parent;
return 0;
-}
+}
+
+static void ssh_password_free(git_cred *cred)
+{
+ git_cred_ssh_password *c = (git_cred_ssh_password *)cred;
+ int pass_len = strlen(c->password);
+
+ git__free(c->username);
+
+ /* Zero the memory which previously held the password */
+ memset(c->password, 0x0, pass_len);
+ git__free(c->password);
+
+ git__free(c);
+}
+
+int git_cred_ssh_password_new(
+ git_cred **cred,
+ const char *username,
+ const char *password)
+{
+ git_cred_ssh_password *c;
+
+ if (!cred)
+ return -1;
+
+ c = (git_cred_ssh_password *)git__malloc(sizeof(git_cred_ssh_password));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_PASSWORD;
+ c->parent.free = ssh_password_free;
+ c->username = git__strdup(username);
+
+ if (!c->username) {
+ git__free(c);
+ return -1;
+ }
+
+ c->password = git__strdup(password);
+
+ if (!c->password) {
+ git__free(c->username);
+ git__free(c);
+ return -1;
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
329 src/transports/ssh.c
View
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * 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 <libssh2.h>
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+#include "smart.h"
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_ssh[] = "ssh://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ 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;
+ char *host;
+ char *port;
+ gitno_socket socket;
+ unsigned connected: 1;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+} ssh_subtransport;
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *delim, *repo;
+
+ if (!cmd)
+ cmd = cmd_uploadpack;
+
+ delim = strchr(url, '/');
+ if (delim == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ repo = delim;
+
+ git_buf_grow(request, strlen(cmd) + strlen(repo) + 2);
+ git_buf_printf(request, "%s %s", cmd, repo);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int ssh_set_error(LIBSSH2_SESSION *session)
+{
+ char *error;
+
+ libssh2_session_last_error(session, &error, NULL, 0);
+ giterr_set(GITERR_NET, "SSH error: %s", error);
+
+ return -1;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ /* It looks like negative values are errors here, and positive values
+ * are the number of bytes sent. */
+ error = libssh2_channel_exec(t->channel, git_buf_cstr(&request));
+
+ if (error >= 0)
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error >= 0 ? error : ssh_set_error(t->session);
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int error;
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+ gitno_buffer buf;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ gitno_buffer_setup(&t->socket, &buf, buffer, buf_size);
+
+ error = libssh2_channel_read(t->channel, buf.data, buf.len);
+ if (error < 0)
+ return ssh_set_error(t->session);
+ else
+ buf.offset = error;
+
+ *bytes_read = buf.offset;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ return libssh2_channel_write(t->channel, buffer, len);
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ git_smart_subtransport_stream **stream,
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd)
+{
+ ssh_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = (ssh_stream *)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->url = git__strdup(url);
+ s->cmd = cmd;
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int ssh_uploadpack_ls(
+ git_smart_subtransport_stream **stream,
+ ssh_subtransport *t,
+ const char *url)
+{
+ ssh_stream *s;
+
+ *stream = NULL;
+
+ if (ssh_stream_alloc(stream, t, url, cmd_uploadpack) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+ t->current_stream = s;
+
+ return 0;
+}
+
+static int ssh_uploadpack(
+ git_smart_subtransport_stream **stream,
+ ssh_subtransport *t)
+{
+ 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_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *)smart_transport;
+ const char *default_port = "22";
+
+ if (!stream)
+ return -1;
+
+ if (!git__prefixcmp(url, prefix_ssh))
+ url += strlen(prefix_ssh);
+
+ if (!t->host || !t->port) {
+ if (gitno_extract_host_and_port(&t->host, &t->port,
+ url, default_port) < 0)
+ return -1;
+ }
+
+ if (!t->connected) {
+ if (!t->owner->cred_acquire_cb) {
+ giterr_set(GITERR_NET, "No credential callback given");
+ return -1;
+ }
+
+ if (gitno_connect(&t->socket, t->host, t->port, 0) < 0)
+ return -1;
+
+ if (libssh2_init(0) < 0) {
+ giterr_set(GITERR_NET, "Failed to init libssh2");
+ return -1;
+ }
+
+ t->session = libssh2_session_init();
+ if (t->session == NULL) {
+ giterr_set(GITERR_NET, "Failed to init SSH session");
+ return -1;
+ }
+
+ libssh2_session_set_blocking(t->session, 1);
+
+ if (libssh2_session_handshake(t->session,
+ (libssh2_socket_t)(t->socket.socket)) < 0)
+ return ssh_set_error(t->session);
+
+ if (t->owner->cred_acquire_cb(&t->cred, t->owner->url,
+ GIT_CREDTYPE_SSH_PASSWORD) < 0)
+ return -1;
+
+ assert(t->cred);
+
+ git_cred_ssh_password *c = (git_cred_ssh_password *)t->cred;
+ if (libssh2_userauth_password(t->session,
+ c->username, c->password) < 0)
+ return ssh_set_error(t->session);
+
+ t->channel = libssh2_channel_open_session(t->session);
+ if (t->channel == NULL)
+ return ssh_set_error(t->session);
+
+ t->connected = 1;
+ }
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(stream, t, url);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(stream, t);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static void ssh_free(git_smart_subtransport *smart_transport)
+{
+ ssh_subtransport *t = (ssh_subtransport *)smart_transport;
+
+ libssh2_channel_close(t->channel);
+ libssh2_channel_free(t->channel);
+
+ libssh2_session_disconnect(t->session, NULL);
+ libssh2_session_free(t->session);
+
+ libssh2_exit();
+
+ gitno_close(&t->socket);
+
+ if (t->cred)
+ t->cred->free(t->cred);
+
+ git__free(t->host);
+ git__free(t->port);
+ git__free(t);
+}
+
+int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owner)
+{
+ ssh_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = (ssh_subtransport *)git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = ssh_action;
+ t->parent.free = ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_SSH */
Something went wrong with that request. Please try again.