From f7158cd79b836d791b188f67b6d54064afb8ed31 Mon Sep 17 00:00:00 2001 From: Brad Morgan Date: Fri, 3 May 2013 16:31:16 -0400 Subject: [PATCH] Push working over ssh --- include/git2/transport.h | 25 +++++++++++++ src/transports/cred.c | 77 ++++++++++++++++++++++++++++++++++++++++ src/transports/ssh.c | 74 ++++++++++++++++++++++++-------------- 3 files changed, 149 insertions(+), 27 deletions(-) diff --git a/include/git2/transport.h b/include/git2/transport.h index 9b9ecc5fc..31572c16a 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -27,6 +27,7 @@ GIT_BEGIN_DECL typedef enum { /* git_cred_userpass_plaintext */ GIT_CREDTYPE_USERPASS_PLAINTEXT = 1, + GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE = 2, } git_credtype_t; /* The base structure for all credential types */ @@ -43,6 +44,14 @@ typedef struct git_cred_userpass_plaintext { char *password; } git_cred_userpass_plaintext; +/* A plaintext username and password */ +typedef struct git_cred_ssh_keyfile_passphrase { + git_cred parent; + char *publickey; + char *privatekey; + char *passphrase; +} git_cred_ssh_keyfile_passphrase; + /** * Creates a new plain-text username and password credential object. * The supplied credential parameter will be internally duplicated. @@ -57,6 +66,22 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new( const char *username, const char *password); +/** + * Creates a new ssh key file and passphrase credential object. + * The supplied credential parameter will be internally duplicated. + * + * @param out The newly created credential object. + * @param publickey The path to the public key of the credential. + * @param privatekey The path to the private key of the credential. + * @param passphrase The passphrase of the credential. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_cred_ssh_keyfile_passphrase_new( + git_cred **out, + const char *publickey, + const char *privatekey, + const char *passphrase); + /** * Signature of a function which acquires a credential object. * diff --git a/src/transports/cred.c b/src/transports/cred.c index ecb026062..83820ec05 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -58,3 +58,80 @@ int git_cred_userpass_plaintext_new( *cred = &c->parent; return 0; } + +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; + + if (!cred) + return -1; + + c = git__malloc(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); + + if (!c->privatekey) { + git__free(c); + return -1; + } + + if (publickey) { + c->publickey = git__strdup(publickey); + + if (!c->publickey) { + git__free(c->privatekey); + git__free(c); + return -1; + } + } else { + c->publickey = NULL; + } + + if (passphrase) { + c->passphrase = git__strdup(passphrase); + + if (!c->passphrase) { + git__free(c->privatekey); + if (c->publickey) { + git__free(c->publickey); + } + git__free(c); + return -1; + } + } else { + c->passphrase = NULL; + } + + *cred = &c->parent; + return 0; +} diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 8f36a6549..935fe580a 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -8,6 +8,7 @@ #include "git2.h" #include "buffer.h" #include "netops.h" +#include "smart.h" #include @@ -21,6 +22,8 @@ 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; @@ -28,8 +31,9 @@ typedef struct { typedef struct { git_smart_subtransport parent; - git_transport *owner; + transport_smart *owner; ssh_stream *current_stream; + git_cred *cred; } ssh_subtransport; /* @@ -40,27 +44,19 @@ typedef struct { static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *delim, *repo; - char host[] = "host="; size_t len; - delim = strchr(url, '/'); + delim = strchr(url, ':'); if (delim == NULL) { giterr_set(GITERR_NET, "Malformed URL"); return -1; } - repo = delim; - - delim = strchr(url, ':'); - if (delim == NULL) - delim = strchr(url, '/'); - - len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; + repo = delim+1; + len = strlen(cmd) + 1 + 1 + strlen(repo) + 1; git_buf_grow(request, len); - git_buf_printf(request, "%04x%s %s%c%s", - (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); - git_buf_put(request, url, delim - url); + git_buf_printf(request, "%s '%s'", cmd, repo); git_buf_putc(request, '\0'); if (git_buf_oom(request)) @@ -78,12 +74,18 @@ static int send_command(ssh_stream *s) if (error < 0) goto cleanup; - /* It looks like negative values are errors here, and positive values - * are the number of bytes sent. */ - error = gitno_send(&s->socket, request.ptr, request.size, 0); + error = libssh2_channel_process_startup( + s->channel, + "exec", + (uint32_t)sizeof("exec") - 1, + request.ptr, + request.size + ); + + if (0 != error) + goto cleanup; - if (error >= 0) - s->sent_command = 1; + s->sent_command = 1; cleanup: git_buf_free(&request); @@ -97,19 +99,18 @@ static int ssh_stream_read( size_t *bytes_read) { ssh_stream *s = (ssh_stream *)stream; - gitno_buffer buf; *bytes_read = 0; if (!s->sent_command && send_command(s) < 0) return -1; - gitno_buffer_setup(&s->socket, &buf, buffer, buf_size); + int rc = libssh2_channel_read(s->channel, buffer, buf_size); - if (gitno_recv(&buf) < 0) + if (rc < 0) return -1; - *bytes_read = buf.offset; + *bytes_read = rc; return 0; } @@ -124,7 +125,12 @@ static int ssh_stream_write( if (!s->sent_command && send_command(s) < 0) return -1; - return gitno_send(&s->socket, buffer, len, 0); + 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) @@ -285,6 +291,17 @@ static int _git_receivepack_ls( if (gitno_connect(&s->socket, host, "22", 0) < 0) goto on_error; + if (t->owner->cred_acquire_cb(&t->cred, + t->owner->url, + user, + GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, + t->owner->cred_acquire_payload) < 0) + return -1; + + assert(t->cred); + + git_cred_ssh_keyfile_passphrase *cred = (git_cred_ssh_keyfile_passphrase *)t->cred; + LIBSSH2_SESSION* session = libssh2_session_init(); if (!session) goto on_error; @@ -306,9 +323,9 @@ static int _git_receivepack_ls( session, user, strlen(user), - NULL, - "/Users/bradfordmorgan/.ssh/id_rsa", - NULL + cred->publickey, + cred->privatekey, + cred->passphrase ); } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); @@ -327,6 +344,9 @@ static int _git_receivepack_ls( libssh2_channel_set_blocking(channel, 1); + s->session = session; + s->channel = channel; + t->current_stream = s; git__free(host); return 0; @@ -412,7 +432,7 @@ int git_smart_subtransport_ssh(git_smart_subtransport **out, git_transport *owne t = git__calloc(sizeof(ssh_subtransport), 1); GITERR_CHECK_ALLOC(t); - t->owner = owner; + t->owner = (transport_smart *)owner; t->parent.action = _git_action; t->parent.close = _git_close; t->parent.free = _git_free;