Commit f7158cd79b836d791b188f67b6d54064afb8ed31

Brad Morgan 2013-05-03T16:31:16

Push working over ssh

diff --git a/include/git2/transport.h b/include/git2/transport.h
index 9b9ecc5..31572c1 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.
@@ -58,6 +67,22 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
 	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.
  *
  * @param cred The newly created credential object.
diff --git a/src/transports/cred.c b/src/transports/cred.c
index ecb0260..83820ec 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 8f36a65..935fe58 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 <libssh2.h>
 
@@ -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;