git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2)

From
ILIlari Liusvaara <ilari.liusvaara@elisanet.fi>
Date
Jan 13, 2010, 13:19 UTC
Message-ID
<1263388786-6880-2-git-send-email-ilari.liusvaara@elisanet.fi>
In-Reply-To
<1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi>
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
 Makefile                               |   23 ++-
 git-over-tls/.gitignore                |    5 +
 git-over-tls/Makefile                  |   46 +++
 git-over-tls/cbuffer.c                 |  504 ++++++++++++++++++++++++++++++++
 git-over-tls/cbuffer.h                 |  304 +++++++++++++++++++
 git-over-tls/certificate.c             |  306 +++++++++++++++++++
 git-over-tls/certificate.h             |   28 ++
 git-over-tls/connect.c                 |  263 +++++++++++++++++
 git-over-tls/connect.h                 |   14 +
 git-over-tls/genkeypair.c              |   38 +++
 git-over-tls/gensrpverifier.c          |  372 +++++++++++++++++++++++
 git-over-tls/getkeyid.c                |  118 ++++++++
 git-over-tls/gits-send-special-command |   22 ++
 git-over-tls/home.c                    |   47 +++
 git-over-tls/home.h                    |   13 +
 git-over-tls/hostkey.c                 |  116 ++++++++
 git-over-tls/hostkey.h                 |   15 +
 git-over-tls/hostkeymanager.c          |  305 +++++++++++++++++++
 git-over-tls/keypairs.c                |   60 ++++
 git-over-tls/keypairs.h                |   16 +
 20 files changed, 2613 insertions(+), 2 deletions(-)
 create mode 100644 git-over-tls/.gitignore
 create mode 100644 git-over-tls/Makefile
 create mode 100644 git-over-tls/cbuffer.c
 create mode 100644 git-over-tls/cbuffer.h
 create mode 100644 git-over-tls/certificate.c
 create mode 100644 git-over-tls/certificate.h
 create mode 100644 git-over-tls/connect.c
 create mode 100644 git-over-tls/connect.h
 create mode 100644 git-over-tls/genkeypair.c
 create mode 100644 git-over-tls/gensrpverifier.c
 create mode 100644 git-over-tls/getkeyid.c
 create mode 100755 git-over-tls/gits-send-special-command
 create mode 100644 git-over-tls/home.c
 create mode 100644 git-over-tls/home.h
 create mode 100644 git-over-tls/hostkey.c
 create mode 100644 git-over-tls/hostkey.h
 create mode 100644 git-over-tls/hostkeymanager.c
 create mode 100644 git-over-tls/keypairs.c
 create mode 100644 git-over-tls/keypairs.h
diff --git a/Makefile b/Makefile
index 4d2b99c..81cc848 100644
--- a/Makefile
+++ b/Makefile
@@ -163,6 +163,10 @@ all::
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
 #
+# Define NO_GNUTLS if you don't want to use GnuTLS.
+#
+# Define NO_SRP if you don't want SRP support.
+#
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
@@ -171,7 +175,6 @@ all::
 # Define NO_PYTHON if you do not want Python scripts or libraries at all.
 #
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
-#
 # The TCL_PATH variable governs the location of the Tcl interpreter
 # used to optimize git-gui for your system.  Only used if NO_TCLTK
 # is not set.  Defaults to the bare 'tclsh'.
@@ -291,7 +294,7 @@ TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
 
-export TCL_PATH TCLTK_PATH
+export TCL_PATH TCLTK_PATH CC
 
 # sparse is architecture-neutral, which means that we need to tell it
 # explicitly what architecture to check for. Fix this up for yours..
@@ -1248,6 +1251,9 @@ endif
 ifdef NO_IPV6
 	BASIC_CFLAGS += -DNO_IPV6
 endif
+ifdef NO_SRP
+	BASIC_CFLAGS += -DDISABLE_SRP
+endif
 ifdef NO_UINTMAX_T
 	BASIC_CFLAGS += -Duintmax_t=uint32_t
 endif
@@ -1399,6 +1405,10 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
+# In case some subdirectory wants to use git API libs.
+GIT_PROGRAM_LINKLIBS = $(LIBS)
+export GIT_PROGRAM_LINKLIBS
+
 BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
 	$(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
@@ -1442,6 +1452,9 @@ endif
 ifndef NO_PERL
 	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
+ifndef NO_GNUTLS
+	$(QUIET_SUBDIR0)git-over-tls $(QUIET_SUBDIR1) prefix='$(prefix_SQ)' all
+endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
@@ -1825,6 +1838,9 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ifndef NO_GNUTLS
+	$(MAKE) -C git-over-tls DESTDIR_BIN='$(DESTDIR_SQ)$(bindir_SQ)' DESTDIR_GITEXEC='$(DESTDIR_SQ)$(gitexec_instdir_SQ)' install
+endif
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 endif
@@ -1956,6 +1972,9 @@ ifndef NO_PERL
 	$(RM) gitweb/gitweb.cgi
 	$(MAKE) -C perl clean
 endif
+ifndef NO_GNUTLS
+	$(MAKE) -C git-over-tls clean
+endif
 ifndef NO_PYTHON
 	$(MAKE) -C git_remote_helpers clean
 endif
diff --git a/git-over-tls/.gitignore b/git-over-tls/.gitignore
new file mode 100644
index 0000000..546e49c
--- /dev/null
+++ b/git-over-tls/.gitignore
@@ -0,0 +1,5 @@
+/git-remote-gits
+/gits-get-key-id
+/gits-generate-keypair
+/gits-generate-srp-verifier
+/gits-hostkey
diff --git a/git-over-tls/Makefile b/git-over-tls/Makefile
new file mode 100644
index 0000000..7804ec7
--- /dev/null
+++ b/git-over-tls/Makefile
@@ -0,0 +1,46 @@
+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))
+helper = git-remote-gits
+programs = gits-get-key-id gits-hostkey gits-generate-keypair
+scripts = gits-send-special-command
+flags=
+
+# These can't be inherited from upper level or they won't work right...
+ifndef V
+	QUIET_CC       = @echo '   ' CC $@;
+	QUIET_LINK     = @echo '   ' LINK $@;
+endif
+
+ifdef NO_SRP
+flags += -DDISABLE_SRP
+else
+programs += gits-generate-srp-verifier
+endif
+
+all: $(programs) $(helper)
+
+git-remote-gits: main.o user.o cbuffer.o srp_askpass.o keypairs.o hostkey.o home.o certificate.o prompt.o misc.o prompt.o connect.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-get-key-id: getkeyid.o certificate.o cbuffer.o home.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+ifndef NO_SRP
+gits-generate-srp-verifier: gensrpverifier.o prompt.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+endif
+
+gits-hostkey: hostkeymanager.o home.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-generate-keypair: genkeypair.o home.o mkcert.o cbuffer.o prompt.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+%.o: %.c
+	$(QUIET_CC)$(CC) $(CLFAGS) -c -o $@ $< $(flags) -I..
+
+install: all
+	$(INSTALL) $(programs) $(scripts) '$(DESTDIR_BIN)'
+	$(INSTALL) $(helper) '$(DESTDIR_GITEXEC)'
+
+clean:
+	$(RM) -f *.o $(programs) $(helper)
diff --git a/git-over-tls/cbuffer.c b/git-over-tls/cbuffer.c
new file mode 100644
index 0000000..e2adec7
--- /dev/null
+++ b/git-over-tls/cbuffer.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "cbuffer.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+#include <sys/uio.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/*
+ * NOTE: This code may not crash, call exit or anything similar unless
+ * program state is corrupt or call parameters are completely invalid.
+ * It also may not call Git APIs.
+ */
+
+struct cbuffer
+{
+	/* Base address for data area. */
+	unsigned char *cb_base;
+	/* Amount of free space. */
+	size_t cb_free;
+	/* Amount of used space. */
+	size_t cb_used;
+	/* Get pointer (relative to base) */
+	size_t cb_get;
+	/* Put pointer (relative to base) */
+	size_t cb_put;
+	/* Total size */
+	size_t cb_size;
+};
+
+/* Assert that invariants of circular buffer hold. */
+static void assert_invariants(struct cbuffer *cbuf)
+{
+	assert(cbuf->cb_free >= 0 && cbuf->cb_free <= cbuf->cb_size);
+	assert(cbuf->cb_used >= 0 && cbuf->cb_used <= cbuf->cb_size);
+	assert(cbuf->cb_free + cbuf->cb_used == cbuf->cb_size);
+	assert(cbuf->cb_get >= 0 && cbuf->cb_get <= cbuf->cb_size);
+	assert(cbuf->cb_put >= 0 && cbuf->cb_put <= cbuf->cb_size);
+	if (cbuf->cb_get == cbuf->cb_put)
+		assert(cbuf->cb_free == 0 || cbuf->cb_used == 0);
+	else if (cbuf->cb_get < cbuf->cb_put)
+		assert(cbuf->cb_get + cbuf->cb_used == cbuf->cb_put);
+	else
+		assert(cbuf->cb_put + cbuf->cb_free == cbuf->cb_get);
+}
+
+/* Ack specified amount of data written. */
+static void ack_write(struct cbuffer *cbuf, size_t wsize)
+{
+	assert_invariants(cbuf);
+	assert(wsize <= cbuf->cb_free);
+	/*
+	 * Writing data decreses free space, increases used space,
+	 * increases put pointer, but it will wrap around if it
+	 * goes past end of buffer.
+	 */
+	cbuf->cb_free -= wsize;
+	cbuf->cb_used += wsize;
+	cbuf->cb_put += wsize;
+	if (cbuf->cb_put >= cbuf->cb_size)
+		cbuf->cb_put -= cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+/* Ack specified amount of data read (removing it). */
+static void ack_read(struct cbuffer *cbuf, size_t rsize)
+{
+	assert_invariants(cbuf);
+	assert(rsize <= cbuf->cb_used);
+	/*
+	 * Reading data decreses used space, increases free space,
+	 * increases get pointer, but it will wrap around if it
+	 * goes past end of buffer.
+	 */
+	cbuf->cb_free += rsize;
+	cbuf->cb_used -= rsize;
+	cbuf->cb_get += rsize;
+	if (cbuf->cb_get >= cbuf->cb_size)
+		cbuf->cb_get -= cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize)
+{
+	struct cbuffer *cbuf = (struct cbuffer*)malloc(sizeof(
+		struct cbuffer));
+	if (cbuf) {
+		cbuf->cb_base = data;
+		cbuf->cb_size = cbuf->cb_free = datasize;
+		cbuf->cb_used = cbuf->cb_get = cbuf->cb_put = 0;
+		assert_invariants(cbuf);
+	}
+	return cbuf;
+}
+
+void cbuffer_destroy(struct cbuffer *cbuf)
+{
+	if (cbuf) {
+		assert_invariants(cbuf);
+		free(cbuf);
+	}
+}
+
+size_t cbuffer_used(struct cbuffer *cbuf)
+{
+	assert_invariants(cbuf);
+	return cbuf->cb_used;
+}
+
+size_t cbuffer_free(struct cbuffer *cbuf)
+{
+	assert_invariants(cbuf);
+	return cbuf->cb_free;
+}
+
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to read too much. */
+	if (toread > cbuf->cb_used)
+		return -1;
+
+	size_t tocopy = toread;
+
+	/*
+	 * Limit the amount of data read to amount fits inside single
+	 * segment. If get pointer is not greater or equal to put pointer,
+	 * then entiere used space is only single segment.
+	 */
+	if (cbuf->cb_get >= cbuf->cb_put)
+		if (tocopy > cbuf->cb_size - cbuf->cb_get)
+			tocopy = cbuf->cb_size - cbuf->cb_get;
+
+	if (tocopy > 0) {
+		/* Copy the segment and mark it read. */
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, tocopy);
+		ack_read(cbuf, tocopy);
+		/* Adjust the request for subsequent segments. */
+		toread -= tocopy;
+		dest += tocopy;
+	}
+
+	/*
+	 * If the read was incomplete, repeat the read request for the
+	 * non-read tail.
+	 */
+	if (toread > 0)
+		return cbuffer_read(cbuf, dest, toread);
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to peek too much. */
+	if (toread > cbuf->cb_used)
+		return -1;
+
+	/* Is the used space as single segment or in two segments? */
+	if (cbuf->cb_get + toread > cbuf->cb_size) {
+		/* Two. We have to compute where segment boundary is and
+		   copy the data as two copies. */
+		size_t firstseg = cbuf->cb_size - cbuf->cb_get;
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, firstseg);
+		memcpy(dest + firstseg, cbuf->cb_base, toread - firstseg);
+	} else {
+		/* One, data can be read as single copy. */
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, toread);
+	}
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+	size_t towrite)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to write too much. */
+	if (towrite > cbuf->cb_free)
+		return -1;
+
+	size_t tocopy = towrite;
+
+	/*
+	 * Limit the amount of data written to amount fits inside single
+	 * segment. If put pointer is not greater or equal to get pointer,
+	 * then entiere free space is only single segment.
+	 */
+	if (cbuf->cb_put >= cbuf->cb_get)
+		if (tocopy > cbuf->cb_size - cbuf->cb_put)
+			tocopy = cbuf->cb_size - cbuf->cb_put;
+
+	if (tocopy > 0) {
+		/* Copy the segment and mark it written. */
+		memcpy(cbuf->cb_base + cbuf->cb_put, src, tocopy);
+		ack_write(cbuf, tocopy);
+		/* Adjust the request for subsequent segments. */
+		towrite -= tocopy;
+		src += tocopy;
+	}
+
+	/*
+	 * If the write was incomplete, repeat the write request for the
+	 * non-written tail.
+	 */
+	if (towrite > 0)
+		return cbuffer_write(cbuf, src, towrite);
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove)
+{
+	assert_invariants(dest);
+	assert_invariants(src);
+
+	/* Check that amount to move isn't too great. */
+	if (tomove > dest->cb_free)
+		return -1;
+	if (tomove > src->cb_used)
+		return -1;
+
+	size_t tocopy = tomove;
+	/*
+	 * Compute maximum number of bytes that is less than amount to
+	 * move and amount of used/free space in current segments in
+	 * both buffers.
+	 */
+	if (dest->cb_put >= dest->cb_get)
+		if (tocopy > dest->cb_size - dest->cb_put)
+			tocopy = dest->cb_size - dest->cb_put;
+	if (src->cb_get >= src->cb_put)
+		if (tocopy > src->cb_size - src->cb_get)
+			tocopy = src->cb_size - src->cb_get;
+
+	if (tocopy > 0) {
+		/* Move the segment. and mark it moved. */
+		memcpy(dest->cb_base + dest->cb_put,
+			src->cb_base +src->cb_get, tocopy);
+		ack_read(src, tocopy);
+		ack_write(dest, tocopy);
+		/* Adjust request for subsequent segments. */
+		tomove -= tocopy;
+	}
+
+	/* If request was incomplete, move the yet unmoved tail. */
+	if (tomove > 0)
+		return cbuffer_move(dest, src, tomove);
+
+	assert_invariants(dest);
+	assert_invariants(src);
+
+	return 0;
+}
+
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd)
+{
+	ssize_t r;
+
+	assert_invariants(cbuf);
+
+	/* Generate EAGAIN if needed (on no free space). */
+	if (cbuf->cb_free == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	/*
+	 * If scatter-gather I/O is available, entiere buffer may be read at
+	 * once. Otherwise only single segment can be read at time.
+	 */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+	struct iovec areas[2];
+	int touse;
+
+	/* One or two segments? */
+	if (cbuf->cb_put >= cbuf->cb_get) {
+		/* Two. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+		areas[0].iov_len = cbuf->cb_size - cbuf->cb_put;
+		areas[1].iov_base = cbuf->cb_base;
+		areas[1].iov_len = cbuf->cb_get;
+		touse = 2;
+	} else {
+		/* One. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+		areas[0].iov_len = cbuf->cb_get - cbuf->cb_put;
+		touse = 1;
+	}
+	r = readv(fd, areas, touse);
+#else
+	/* Read into current segment. */
+	if (cbuf->cb_put >= cbuf->cb_get)
+		r = read(fd, cbuf->cb_base + cbuf->cb_put,
+			cbuf->cb_size - cbuf->cb_put);
+	else
+		r = read(fd, cbuf->cb_base + cbuf->cb_put,
+			cbuf->cb_get - cbuf->cb_put);
+#endif
+	/* Ack any successfully read data as written. */
+	if (r > 0)
+		ack_write(cbuf, (size_t)r);
+
+	assert_invariants(cbuf);
+
+	return r;
+}
+
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd)
+{
+	ssize_t r;
+
+	assert_invariants(cbuf);
+
+	/* Generate EAGAIN if needed (on no used space). */
+	if (cbuf->cb_used == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	/*
+	 * If scatter-gather I/O is available, entiere buffer may be written
+	 * at once. Otherwise only single segment can be written at time.
+	 */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+	struct iovec areas[2];
+	int touse;
+
+	/* One or two segments? */
+	if (cbuf->cb_get >= cbuf->cb_put) {
+		/* Two. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+		areas[0].iov_len = cbuf->cb_size - cbuf->cb_get;
+		areas[1].iov_base = cbuf->cb_base;
+		areas[1].iov_len = cbuf->cb_put;
+		touse = 2;
+	} else {
+		/* One. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+		areas[0].iov_len = cbuf->cb_put - cbuf->cb_get;
+		touse = 1;
+	}
+	r = writev(fd, areas, touse);
+#else
+	/* Write current segment. */
+	if (cbuf->cb_get >= cbuf->cb_put)
+		r = write(fd, cbuf->cb_base + cbuf->cb_get,
+			cbuf->cb_size - cbuf->cb_get);
+	else
+		r = write(fd, cbuf->cb_base + cbuf->cb_get,
+			cbuf->cb_put - cbuf->cb_get);
+#endif
+	/* Ack any successfully written data as read. */
+	if (r > 0)
+		ack_read(cbuf, (size_t)r);
+
+	assert_invariants(cbuf);
+
+	return r;
+}
+
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length)
+{
+	assert_invariants(cbuf);
+
+	/* Compute segment base. */
+	*base = cbuf->cb_base + cbuf->cb_get;
+
+	if (!cbuf->cb_used) {
+		/* No used space -> empty segment. */
+		*length = 0;
+	} else if (cbuf->cb_get >= cbuf->cb_put) {
+		/* High segment is current. */
+		*length = cbuf->cb_size - cbuf->cb_get;
+	} else {
+		/* Low segment is current. */
+		*length = cbuf->cb_put - cbuf->cb_get;
+	}
+
+	assert_invariants(cbuf);
+}
+
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length)
+{
+	assert_invariants(cbuf);
+	/*
+	 * This doesn't handle read being longer than single segment right,
+	 * but that's undefined anyway.
+	 */
+	ack_read(cbuf, length);
+	assert_invariants(cbuf);
+}
+
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length)
+{
+	assert_invariants(cbuf);
+
+	/* Compute segment base. */
+	*base = cbuf->cb_base + cbuf->cb_put;
+
+	if (!cbuf->cb_free) {
+		/* No free space -> empty segment. */
+		*length = 0;
+	} else if (cbuf->cb_put >= cbuf->cb_get) {
+		/* High segment is current. */
+		*length = cbuf->cb_size - cbuf->cb_put;
+	} else {
+		/* Low segment is current. */
+		*length = cbuf->cb_get - cbuf->cb_put;
+	}
+
+	assert_invariants(cbuf);
+}
+
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length)
+{
+	assert_invariants(cbuf);
+	/*
+	 * This doesn't handle write being longer than single segment right,
+	 * but that's undefined anyway.
+	 */
+	ack_write(cbuf, length);
+	assert_invariants(cbuf);
+}
+
+
+void cbuffer_clear(struct cbuffer *cbuf)
+{
+	/*
+	 * Just resetting pointers and values to initial defaults clears
+	 * all data.
+	 */
+	cbuf->cb_used = cbuf->cb_put = cbuf->cb_get = 0;
+	cbuf->cb_free = cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do read request. */
+	if (limit > cbuffer_used(cbuf))
+		limit = cbuffer_used(cbuf);
+	cbuffer_read(cbuf, dest, limit);
+	return limit;
+}
+
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do write request. */
+	if (limit > cbuffer_free(cbuf))
+		limit = cbuffer_free(cbuf);
+	cbuffer_write(cbuf, src, limit);
+	return limit;
+}
+
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do move request. */
+	if (limit > cbuffer_free(dest))
+		limit = cbuffer_free(dest);
+	if (limit > cbuffer_used(src))
+		limit = cbuffer_used(src);
+	cbuffer_move(dest, src, limit);
+	return limit;
+}
+
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src)
+{
+	size_t limit;
+	/*
+	 * Limit the request to maximum possible and do move request.
+	 * The move lacks limit so use free space in destination as
+	 * first limit.
+	 */
+	limit = cbuffer_free(dest);
+	if (limit > cbuffer_used(src))
+		limit = cbuffer_used(src);
+	cbuffer_move(dest, src, limit);
+	return limit;
+}
diff --git a/git-over-tls/cbuffer.h b/git-over-tls/cbuffer.h
new file mode 100644
index 0000000..4e07c5b
--- /dev/null
+++ b/git-over-tls/cbuffer.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _cbuffer__h__included__
+#define _cbuffer__h__included__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminology:
+ *	Segment:
+ *		Memory-contigious range extending from get pointer, put
+ *		pointer or start of circular buffer till used/free type
+ *		changes.
+ *	Current segment:
+ *		Segment starting from get pointer or put pointer.
+ *
+ */
+
+/* Primary circular buffer structure. Opaque type. */
+struct cbuffer;
+
+/*
+ * Create new circular buffer using specified data area. The data area
+ * is not copied and must remain until circular buffer is destroyed.
+ *
+ * Inputs:
+ *	data		The data area to back the circular buffer.
+ *	datasize	Size of backing data area.
+ *
+ * Outputs:
+ *	return value	The newly created circular buffer, or NULL
+ *			if out of memory.
+ */
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize);
+
+
+/*
+ * Free circular buffer. Data area is not freed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to free.
+ */
+void cbuffer_destroy(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes used in buffer (how many bytes can be read without
+ * writes).
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to interrogate.
+ *
+ * Outputs:
+ *	return value	Number of bytes data in buffer.
+ */
+size_t cbuffer_used(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes free in buffer (how many bytes can be written
+ * without reads).
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to interrogate.
+ *
+ * Outputs:
+ *	return value	Number of bytes free in buffer.
+ */
+size_t cbuffer_free(struct cbuffer *cbuf);
+
+/*
+ * Peek specified number of bytes from buffer. The bytes are not removed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to peek.
+ *	dest		Destination buffer to store the peeked data to.
+ *	toread		Number of bytes to peek.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			amount of data.
+ *
+ */
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Read specified number of bytes from buffer. The bytes read are
+ * removed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to read.
+ *	dest		Destination buffer to store the read data to.
+ *	toread		Number of bytes to read.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			amount of data.
+ */
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Write specified number of bytes to buffer.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to write.
+ *	src		Buffer to read the written data from.
+ *	towrite		Number of bytes to write.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			free space.
+ */
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+	size_t towrite);
+
+/*
+ * Move specified number of bytes from buffer to another. The data is
+ * removed from source buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source cirrcular buffer.
+ *	tomove		Number of bytes to move.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if insufficient source buffer
+ *			data or insufficient destination buffer space.
+ */
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove);
+
+/*
+ * Call read on file descriptor. The call tries to read as much as
+ * possible in one go (up to entiere free space of destination
+ * buffer).
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to store the data to.
+ *	fd		File descriptor to read.
+ *
+ * Outputs:
+ *	Return value	Number of bytes read (>0) on success, 0 if
+ *			EOF on file descriptor or -1 if read failed.
+ *	errno		Set by read() or readv() on failure.
+ *			EAGAIN if there is no free space in circular buffer.
+ */
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Call write on file descriptor. The call tries to write as much as
+ * possible in one go (up to entiere used space of soruce
+ * buffer).
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read data from.
+ *	fd		File descriptor to write.
+ *
+ * Outputs:
+ *	Return value	Number of bytes written (>=0) on success,
+ *			-1 if write failed.
+ *	errno		Set by write() or writev() on failure.
+ *			EAGAIN if there is no used space in circular buffer.
+ */
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Fill buffer read segment for direct from memory read operation on
+ * circular buffer. Any read operation on buffer invalidates segment.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read from.
+ *
+ * Outputs:
+ *	base		Start address of segemnt is written here.
+ *	length		Length of segment is written here.
+ *
+ * Notes:
+ *	- Returned length of 0 is only possible if there is no used
+ *	  space in buffer.
+ *	- The entiere used space may not be returned in single segment.
+ */
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length);
+
+/*
+ * Commit read segment after direct read operation, marking data as
+ * read. The read bytes are removed.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer read from.
+ *	length		Length of segment to mark as read. Must
+ *			be at most the length gotten from
+ *			cbuffer_fill_r_segment.
+ */
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Fill buffer write segment for direct to memory write operation on
+ * circular buffer. Any write operation on buffer invalidates segment.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to write to.
+ *
+ * Outputs:
+ *	base		Start address of segment is written here.
+ *	length		Length of segment is written here.
+ *
+ * Notes:
+ *	- Returned length of 0 is only possible if there is no free
+ *	  space in buffer.
+ *	- The entiere free space may not be returned in single segment.
+ */
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length);
+
+/*
+ * Commit write segment after direct write operation, marking data as
+ * written
+ *
+ * Inputs:
+ *	cbuf		Circular buffer written to.
+ *	length		Length of segment to mark as written. Must
+ *			be at most the length gotten from
+ *			cbuffer_fill_w_segment.
+ */
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Clear all data in cbuffer, freeing any used space.
+ *
+ * Inputs:
+ *	cbuf		Cirecular buffer to clear.
+ */
+void cbuffer_clear(struct cbuffer *cbuf);
+
+/*
+ * Read number of bytes from circular buffer smaller than limit.
+ * The read bytes are removed.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read.
+ *	dest		Destination to store the read data.
+ *	limit		Maximum number of bytes to read.
+ *
+ * Outputs:
+ *	Return value	Number of bytes read.
+ */
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+	size_t limit);
+
+/*
+ * Write number of bytes to circular buffer smaller than limit.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to write.
+ *	dest		Source to read the wrritten data.
+ *	limit		Maximum number of bytes to write.
+ *
+ * Outputs:
+ *	Return value	Number of bytes written.
+ */
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+	size_t limit);
+
+/*
+ * Move number of bytes from circular buffer to another smaller than limit.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source circular buffer.
+ *	limit		Maximum number of bytes to move.
+ *
+ * Outputs:
+ *	Return value	Number of bytes moved.
+ */
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+	size_t limit);
+
+/*
+ * Move as much bytes from circular buffer to another as possible.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source circular buffer.
+ *
+ * Outputs:
+ *	Return value	Number of bytes moved.
+ */
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/git-over-tls/certificate.c b/git-over-tls/certificate.c
new file mode 100644
index 0000000..adeb776
--- /dev/null
+++ b/git-over-tls/certificate.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "cbuffer.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#include "run-command.h"
+#endif
+
+#define CERT_MAX 65536
+
+static long read_short(struct cbuffer *buffer)
+{
+	unsigned char x[2];
+	if (cbuffer_read(buffer, x, 2) < 0)
+		return -1;
+
+	return ((long)x[0] << 8) | ((long)x[1]);
+}
+
+
+static int unseal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,
+	const char *unsealer)
+{
+	struct child_process child;
+	char **argv;
+	char *unsealer_copy;
+	int splits = 0;
+	int escape = 0;
+	int ridx, widx, tidx;
+	const char *i;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	for (i = unsealer; *i; i++) {
+		if (escape)
+			escape = 0;
+		else if (*i == '\\')
+			escape = 1;
+		else if (*i == ' ')
+			splits++;
+	}
+
+	argv = xmalloc((splits + 2) * sizeof(char*));
+	argv[splits + 1] = NULL;
+
+	unsealer_copy = xstrdup(unsealer);
+	argv[0] = unsealer_copy;
+
+	ridx = 0;
+	widx = 0;
+	tidx = 1;
+	escape = 0;
+	while (unsealer_copy[ridx]) {
+		if (escape) {
+			escape = 0;
+			unsealer_copy[widx++] = unsealer_copy[ridx++];
+		} else if (unsealer_copy[ridx] == '\\') {
+			ridx++;
+			escape = 1;
+		} else if (unsealer_copy[ridx] == ' ') {
+			unsealer_copy[widx++] = '\0';
+			argv[tidx++] = unsealer_copy + widx;
+			ridx++;
+		} else
+			unsealer_copy[widx++] = unsealer_copy[ridx++];
+	}
+	unsealer_copy[widx] = '\0';
+
+	memset(&child, 0, sizeof(child));
+	child.argv = (const char**)argv;
+	child.in = -1;
+	child.out = -1;
+	child.err = 0;
+	if (start_command(&child))
+		die("Running keypair unsealer command failed");
+
+	while (1) {
+		int bound;
+		fd_set rf;
+		fd_set wf;
+		int r;
+
+		FD_ZERO(&rf);
+		FD_ZERO(&wf);
+		FD_SET(child.out, &rf);
+		if (cbuffer_used(sealed))
+			FD_SET(child.in, &wf);
+		else
+			close(child.in);
+
+		if (cbuffer_used(sealed))
+			bound = ((child.out > child.in) ? child.out :
+				child.in) + 1;
+		else
+			bound = child.out + 1;
+
+		r = select(bound, &rf, &wf, NULL, NULL);
+		if (r < 0 && r != EINTR)
+			die_errno("Select failed");
+		if (r < 0) {
+			FD_ZERO(&rf);
+			FD_ZERO(&wf);
+			perror("select");
+		}
+
+		if (FD_ISSET(child.out, &rf)) {
+			r = cbuffer_read_fd(unsealed, child.out);
+			if (r < 0 && errno != EINTR && errno != EAGAIN)
+				die_errno("Read from unsealer failed");
+			if (r < 0 && errno == EAGAIN)
+				if (!cbuffer_free(unsealed))
+					die("Keypair too big");
+			if (r < 0)
+				perror("read");
+			if (r == 0)
+				break;
+		}
+
+		if (FD_ISSET(child.in, &wf)) {
+			r = cbuffer_write_fd(sealed, child.in);
+			if (r < 0 && errno == EPIPE)
+				die("Unsealer exited unexpectedly");
+			if (r < 0 && errno != EINTR && errno != EAGAIN)
+				die_errno("Write to unsealer failed");
+			if (r < 0)
+				perror("write");
+		}
+	}
+
+	if (finish_command(&child))
+		die("Keypair unsealer command failed");
+
+	return 0;
+}
+
+
+struct certificate parse_certificate(const char *name, int *errorcode)
+{
+	struct cbuffer *sealed = NULL;
+	struct cbuffer *unsealed = NULL;
+	unsigned char sealed_buf[CERT_MAX];
+	unsigned char unsealed_buf[CERT_MAX];
+	struct certificate cert;
+	int fd;
+	unsigned char head[10];
+	long tmp;
+
+	*errorcode = CERTERR_OK;
+	cert.public_key.data = NULL;
+	cert.public_key.size = 0;
+	cert.private_key.data = NULL;
+	cert.private_key.size = 0;
+
+	sealed = cbuffer_create(sealed_buf, CERT_MAX);
+	if (!sealed)
+		die("Ran out of memory");
+
+	unsealed = cbuffer_create(unsealed_buf, CERT_MAX);
+	if (!unsealed)
+		die("Ran out of memory");
+
+	fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT)
+			*errorcode = CERTERR_NOCERT;
+		else
+			*errorcode = CERTERR_CANTREAD;
+		goto out_unsealed;
+	}
+
+	while (1) {
+		ssize_t r = cbuffer_read_fd(sealed, fd);
+		if (r == 0)
+			break;
+		if (r < 0 && errno == EAGAIN) {
+			if (!cbuffer_free(sealed)) {
+				*errorcode = CERTERR_TOOBIG;
+				goto out_close;
+			}
+		} else if (r < 0 && errno != EINTR) {
+			*errorcode = CERTERR_CANTREAD;
+			goto out_close;
+		}
+	}
+
+	head[9] = 0;
+	if (cbuffer_read(sealed, head, 9) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	if (strcmp((char*)head, "GITSSCERT") &&
+		strcmp((char*)head, "GITSUCERT")) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+
+	if (!strcmp((char*)head, "GITSSCERT")) {
+		/* Sealed certificate. */
+		char *unsealer;
+		int s;
+		tmp = read_short(sealed);
+		if (tmp <= 0) {
+			*errorcode = CERTERR_INVALID;
+			goto out_close;
+		}
+		unsealer = xmalloc(tmp + 1);
+		unsealer[tmp] = '\0';
+		if (cbuffer_read(sealed, (unsigned char*)unsealer, tmp) < 0) {
+			free(unsealer);
+			*errorcode = CERTERR_INVALID;
+			goto out_close;
+		}
+		s = unseal_cert(sealed, unsealed, unsealer);
+		free(unsealer);
+		if (s < 0) {
+			*errorcode = s;
+			goto out_close;
+		}
+	} else {
+		/* Unsealed certificate. */
+		cbuffer_move_nolimit(unsealed, sealed);
+	}
+
+	cert.private_key.data = NULL;
+	cert.public_key.data = NULL;
+
+	tmp = read_short(unsealed);
+	if (tmp < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	cert.private_key.size = tmp;
+	cert.private_key.data = (unsigned char*)gnutls_malloc(tmp);
+	if (!cert.private_key.data)
+		die("Ran out of memory");
+
+	if (cbuffer_read(unsealed, cert.private_key.data,
+		cert.private_key.size) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_private;
+	}
+
+	tmp = read_short(unsealed);
+	if (tmp < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	cert.public_key.size = tmp;
+	cert.public_key.data = (unsigned char*)gnutls_malloc(tmp);
+	if (!cert.public_key.data)
+		die("Ran out of memory");
+
+	if (cbuffer_read(unsealed, cert.public_key.data,
+		cert.public_key.size) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_public;
+	}
+
+	if (cbuffer_used(unsealed)) {
+		*errorcode = CERTERR_INVALID;
+		goto out_public;
+	}
+
+	goto out_close;
+
+out_public:
+	gnutls_free(cert.private_key.data);
+out_private:
+	gnutls_free(cert.private_key.data);
+out_close:
+	close(fd);
+out_unsealed:
+	cbuffer_destroy(unsealed);
+	cbuffer_destroy(sealed);
+	return cert;
+}
+
+const char *cert_parse_strerr(int errcode)
+{
+	switch(errcode) {
+	case CERTERR_OK:
+		return "Success";
+	case CERTERR_NOCERT:
+		return "No such keypair";
+	case CERTERR_INVALID:
+		return "Keypair file corrupt";
+	case CERTERR_CANTREAD:
+		return "Can't read keypair file";
+	case CERTERR_TOOBIG:
+		return "Keypair too big";
+	}
+	return "<Unknown error>";
+}
diff --git a/git-over-tls/certificate.h b/git-over-tls/certificate.h
new file mode 100644
index 0000000..5ee355a
--- /dev/null
+++ b/git-over-tls/certificate.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _certificate__h__included__
+#define _certificate__h__included__
+
+#include <gnutls/gnutls.h>
+
+#define CERTERR_OK		0
+#define CERTERR_NOCERT		-2
+#define CERTERR_INVALID		-3
+#define CERTERR_CANTREAD	-4
+#define CERTERR_TOOBIG		-5
+
+struct certificate
+{
+	gnutls_datum_t public_key;
+	gnutls_datum_t private_key;
+};
+
+struct certificate parse_certificate(const char *name, int *errorcode);
+const char *cert_parse_strerr(int errcode);
+
+#endif
diff --git a/git-over-tls/connect.c b/git-over-tls/connect.c
new file mode 100644
index 0000000..8f19bc8
--- /dev/null
+++ b/git-over-tls/connect.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "connect.h"
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifndef WIN32
+#include <sys/un.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+static int connect_unix(const char* path)
+{
+#ifndef WIN32
+	struct sockaddr_un saddru;
+	int fd, ret, plen;
+
+	if (strlen(path) > UNIX_PATH_MAX - 1)
+		die("Unix socket path too long");
+
+	saddru.sun_family = AF_UNIX;
+	strcpy(saddru.sun_path, path);
+	if (*saddru.sun_path == '@')
+		*saddru.sun_path = '\0';
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (fd < 0)
+		die_errno("Can't create socket");
+	if (*path == '@')
+		plen = (int)((char*)saddru.sun_path - (char*)&saddru) +
+			strlen(path);
+	else
+		plen = (int)sizeof(saddru);
+	ret = connect(fd, (struct sockaddr*)&saddru, plen);
+	if (ret < 0) {
+		die_errno("Can't connect to %s", path);
+	}
+	return fd;
+#else
+	die("Unix domain sockets not supported by this build");
+#endif
+}
+
+static int connect_address(const char* host, unsigned short port,
+	int protocol, struct sockaddr* addr, int size)
+{
+	char address[1024];
+	int fd, ret;
+	const unsigned char* _addr;
+
+	fd = socket(addr->sa_family, SOCK_STREAM, protocol);
+	if (fd < 0) {
+		error("Can't create socket: %s", strerror(errno));
+		return -1;
+	}
+	switch(addr->sa_family) {
+	case AF_INET:
+		_addr = (const unsigned char*)
+			&(((struct sockaddr_in*)addr)->sin_addr.s_addr);
+		sprintf(address, "%u.%u.%u.%u", _addr[0], _addr[1],
+			_addr[2], _addr[3]);
+		break;
+#ifndef NO_IPV6
+	case AF_INET6:
+		_addr = (const unsigned char*)
+			((struct sockaddr_in6*)addr)->sin6_addr.s6_addr;
+		sprintf(address, "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+			"%02X%02X:%02X%02X:%02X%02X:%02X%02X",
+			_addr[0], _addr[1], _addr[2], _addr[3],
+			_addr[4], _addr[5], _addr[6], _addr[7],
+			_addr[8], _addr[9], _addr[10], _addr[11],
+			_addr[12], _addr[13], _addr[14], _addr[15]);
+		break;
+#endif
+	default:
+		sprintf(address, "<unknown address type>");
+		break;
+	}
+	ret = connect(fd, addr, size);
+	if (ret < 0) {
+		error("Can't connect to host %s[%s] port %u: %s",
+			host, address, port, strerror(errno));
+		return -1;
+	}
+	return fd;
+}
+
+int connect_gethostbyname(const char* _host,
+	unsigned short _port, uint32_t scope)
+{
+#ifndef NO_IPV6
+	struct addrinfo hints;
+	struct addrinfo* returned;
+	char port[10];
+	int fd, ret;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_STREAM;
+
+	sprintf(port, "%u", _port);
+	ret = getaddrinfo(_host, port, &hints, &returned);
+	if (ret)
+		die("getaddrinfo(%s, %s, ...): %s", _host, port,
+			gai_strerror(ret));
+
+	while (returned) {
+		if (returned->ai_family == AF_INET6)
+			((struct sockaddr_in6*)returned->ai_addr)->
+				sin6_scope_id = scope;
+		else if (scope)
+			warning("Scope is ignored for non-IPv6 addresses");
+
+		fd = connect_address(_host, _port, returned->ai_protocol,
+			returned->ai_addr, returned->ai_addrlen);
+
+		if (fd >= 0)
+			goto out;
+		returned = returned->ai_next;
+	}
+
+	die("Can't connect to host %s", _host);
+
+out:
+	freeaddrinfo(returned);
+	return fd;
+#else
+	struct hostent *host;
+	int fd;
+	static struct sockaddr_in saddr4;
+
+	host = gethostbyname(_host);
+	if (!host || !host->h_addr)
+		die("Can't find host %s", _host);
+
+next_address:
+	if (host->h_addrtype == AF_INET) {
+		memset(&saddr4, 0, sizeof(saddr4));
+		saddr4.sin_family = AF_INET;
+		memcpy(&saddr4.sin_addr, host->h_addr_list[0], 4);
+		saddr4.sin_port = htons(_port);
+		fd = connect_address(_host, _port, 0,
+			(struct sockaddr*)&saddr4,
+			sizeof(struct sockaddr_in));
+		if (scope)
+			warning("Scope is ignored for non-IPv6 addresses");
+	} else
+		die("Host %s has unknown address type", _host);
+
+	if (fd >= 0)
+		goto out;
+
+	host->h_addr_list++;
+	if (host->h_addr_list)
+		goto next_address;
+
+	if (scope > 0)
+		die("Can't connect to host %s%%u", _host, scope);
+	else
+		die("Can't connect to host %s", _host);
+out:
+	return fd;
+#endif
+}
+
+/* Parse character as base-10 digit. */
+static int char_to_int(char ch)
+{
+	switch(ch) {
+	case '0':
+		return 0;
+	case '1':
+		return 1;
+	case '2':
+		return 2;
+	case '3':
+		return 3;
+	case '4':
+		return 4;
+	case '5':
+		return 5;
+	case '6':
+		return 6;
+	case '7':
+		return 7;
+	case '8':
+		return 8;
+	case '9':
+		return 9;
+	default:
+		return -1;
+	}
+}
+
+static uint32_t touint32_t(const char* num)
+{
+	uint32_t x = 0;
+	unsigned i;
+
+	/* Blank string is not valid number. */
+	if (!*num)
+		die("Invalid scope id '%s'", num);
+
+	/* 0 is special case, makes it easier to deal with zeros. */
+	if (!strcmp(num, "0"))
+		return 0;
+
+	/*
+	 * Valid uints numbers are 0-2^32-1, but because we handled 0 as
+	 * special case, the valid range can be 1-2^32-1 here.
+	 */
+	for (i = 0; num[i]; i++) {
+		char ch = char_to_int(num[i]);
+		if (ch < 0)
+			die("Invalid scope id '%s'", num);
+		if (ch == 0 && x == 0)
+			die("Invalid scope id '%s'", num);
+		if (x > 429496729 || (x > 429496728 && ch > 5))
+			die("Invalid scope id '%s'", num);
+		x = x * 10 + ch;
+	}
+	return x;
+}
+
+int connect_host(const char* _host, unsigned short _port)
+{
+	uint32_t scope = 0;
+	char* scopestart;
+	char* hostcopy;
+
+	if (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/'))
+		return connect_unix(_host);
+
+	hostcopy = xmalloc(strlen(_host) + 1);
+	strcpy(hostcopy, _host);
+	scopestart = strchr(hostcopy, '%');
+	if (scopestart) {
+		*(scopestart++) = '\0';
+		scope = touint32_t(scopestart);
+	}
+
+	return connect_gethostbyname(hostcopy, _port, scope);
+}
diff --git a/git-over-tls/connect.h b/git-over-tls/connect.h
new file mode 100644
index 0000000..90e36cd
--- /dev/null
+++ b/git-over-tls/connect.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _connect__h__included__
+#define _connect__h__included__
+
+//Returns connected fd or dies.
+int connect_host(const char* host, unsigned short port);
+
+#endif
diff --git a/git-over-tls/genkeypair.c b/git-over-tls/genkeypair.c
new file mode 100644
index 0000000..4f2142c
--- /dev/null
+++ b/git-over-tls/genkeypair.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void write_cert(int server_mode);
+
+static void do_help()
+{
+	printf("gits-generate-keypair: User keypair generator.\n");
+	printf("Command line options:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("\n");
+	printf("Note: Keep generated keypair files private!\n");
+	printf("Use gits-get-key-name to get public short\n");
+	printf("representation of keypair for authorization.\n");
+	printf("\n");
+	printf("WARNING: Don't let keypairs to be tampered with!\n");
+	printf("WARNING: Tampered keypairs may do very nasty things\n");
+	printf("WARNING: if used.\n");
+	exit(0);
+}
+
+
+int main(int argc, char **argv)
+{
+	if (argc > 1 && !strcmp(argv[1], "--help"))
+		do_help();
+	write_cert(0);
+	return 0;
+}
diff --git a/git-over-tls/gensrpverifier.c b/git-over-tls/gensrpverifier.c
new file mode 100644
index 0000000..751854a
--- /dev/null
+++ b/git-over-tls/gensrpverifier.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "prompt.h"
+#include <stdio.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static void do_help()
+{
+	printf("gits-generate-srp-verifier: Generate SRP verifiers for\n");
+	printf("password authentication\n");
+	printf("Command line:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("\n");
+	printf("Note: Server needs SRP verifier in order to do password\n");
+	printf("authentication. Usernames are assigned by repostiory\n");
+	printf("hosting admin, just using arbitrary names doesn't work.\n");
+	exit(0);
+}
+
+
+struct field
+{
+	const char *f_name;
+	const char *f_generator;
+	const char *f_prime;
+};
+
+struct field fields[] = {
+	{
+	"standard 1024 bit field", "2",
+	"Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE"
+	"3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Z"
+	"jl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ"
+	},
+	{
+	"standard 1536 bit field", "2",
+	"dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5"
+	"LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTa"
+	"u/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtm"
+	"cIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx"
+	},
+	{
+	"standard 2048 bit field", "2",
+	"2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJt"
+	"L8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL"
+	".NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nq"
+	"ha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXq"
+	"vp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUD"
+	"o/DQEyW.d4H.UIlzp"
+	},
+	{NULL, NULL, NULL}
+};
+
+
+unsigned char xfact[16] = {
+0x9D, 0x0F, 0x49, 0xD4,
+0x73, 0x88, 0xC7, 0xFF,
+0xFD, 0x24, 0x6A, 0x0F,
+0x94, 0x78, 0x0C, 0x14
+};
+
+unsigned char rnd[16] = {
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+
+static void add(unsigned char *res, unsigned char *a, unsigned char *b)
+{
+	unsigned char carry = 0;
+	unsigned i;
+
+	for (i = 0; i < 16; i++) {
+		unsigned char newcarry = 0;
+
+		if ((unsigned char)(a[i] + b[i]) < a[i])
+			newcarry++;
+		res[i] = a[i] + b[i];
+		if (res[i] + carry < res[i])
+			newcarry++;
+		res[i] += carry;
+		carry = newcarry;
+	}
+	while (carry) {
+		carry = 159;
+
+		for (i = 0; i < 16; i++) {
+			int newcarry = 0;
+
+			if ((unsigned char)(res[i] + carry) < res[i])
+				newcarry++;
+			res[i] += carry;
+			carry = newcarry;
+		}
+	}
+	for (i = 1; i < 16; i++)
+		if (res[i] < 255)
+			goto skip;
+
+	if (res[0] > 0x60) {
+		for (i = 1; i < 16; i++)
+			res[i] = 0;
+		res[0] -= 0x61;
+	}
+skip:
+	;
+}
+
+void update_xfact()
+{
+	unsigned char xfact2[16];
+	unsigned char xfact4[16];
+	unsigned char xfact5[16];
+	add(xfact2, xfact, xfact);
+	add(xfact4, xfact2, xfact2);
+	add(xfact5, xfact4, xfact);
+	memcpy(xfact, xfact5, 16);
+}
+
+void update_rnd(unsigned char ch)
+{
+	unsigned char rndt[16];
+	unsigned i;
+	for (i = 0; i < 8; i++) {
+		if ((ch >> i) % 2) {
+			add(rndt, rnd, xfact);
+			memcpy(rnd, rndt, 16);
+		}
+		update_xfact();
+	}
+}
+
+void update_rnd_str(const char *ch)
+{
+	while (ch && *ch)
+		update_rnd((unsigned char)*(ch++));
+}
+
+
+void decode_element(gnutls_datum_t *decode, const char *encoded)
+{
+	int s;
+	gnutls_datum_t _base64;
+	size_t base64len, reslen, reslen2;
+
+	base64len = strlen(encoded);
+	reslen2 = reslen = (3 * base64len + 1) / 4;
+
+	_base64.data = (unsigned char*)encoded;
+	_base64.size = base64len;
+
+	decode->size = reslen;
+	decode->data = xmalloc(reslen);
+	s = gnutls_srp_base64_decode(&_base64, (char*)decode->data, &reslen2);
+	if (s < 0)
+		die("Unable to decode base64 data");
+	else if (reslen != reslen2)
+		die("Base64 dlength calculation incorrect. Calculated %lu, "
+			"got %lu", (unsigned long)reslen, (unsigned long)reslen2);
+}
+
+unsigned char *encode_element(gnutls_datum_t *data)
+{
+	int s;
+	size_t reslen2;
+	unsigned char *res;
+
+	reslen2 = (4 * data->size + 2) / 3;
+
+	res = xmalloc(reslen2 + 1);
+	s = gnutls_srp_base64_encode(data, (char*)res, &reslen2);
+	if (s < 0)
+		die("Unable to encode base64 data");
+	res[reslen2] = '\0';
+	return res;
+}
+
+char *generate_srp_line(const char *username,
+	const char *password, const char *junk, struct field *field)
+{
+	gnutls_datum_t salt;
+	gnutls_datum_t g;
+	gnutls_datum_t n;
+	gnutls_datum_t res;
+	int s;
+	char *retline = NULL;
+	unsigned char *encoded_salt;
+	unsigned char *encoded_verifier;
+	update_rnd_str(username);
+	update_rnd_str(":::::");
+	update_rnd_str(junk);
+
+	salt.data = rnd;
+	salt.size = 16;
+	decode_element(&g, field->f_generator);
+	decode_element(&n, field->f_prime);
+
+	s = gnutls_srp_verifier(username, password, &salt, &g, &n, &res);
+	if (s < 0)
+		die("Unable to generate SRP verifier: %s",
+			gnutls_strerror(s));
+
+	encoded_verifier = encode_element(&res);
+	encoded_salt = encode_element(&salt);
+
+	retline = xmalloc(5 + strlen(username) +
+		strlen((char*)encoded_salt) +
+		strlen((char*)encoded_verifier) +
+		strlen(field->f_generator) +
+		strlen(field->f_prime));
+	retline[0] = '\0';
+	strcat(retline, username);
+	strcat(retline, ":");
+	strcat(retline, (char*)encoded_salt);
+	strcat(retline, ":");
+	strcat(retline, (char*)encoded_verifier);
+	strcat(retline, ":");
+	strcat(retline, field->f_generator);
+	strcat(retline, ":");
+	strcat(retline, field->f_prime);
+
+	free(encoded_verifier);
+	free(encoded_salt);
+	free(res.data);
+	free(g.data);
+	free(n.data);
+
+	return retline;
+}
+
+#define LINELEN 69
+
+static void flush_to_file(FILE *out, const char *srpline)
+{
+	char linebuffer[LINELEN + 2];
+
+	linebuffer[LINELEN] = '\\';
+	linebuffer[LINELEN + 1] = '\0';
+
+	while (*srpline) {
+		size_t r;
+		strncpy(linebuffer, srpline, LINELEN);
+		r = strlen(srpline);
+		if (r == LINELEN)
+			linebuffer[LINELEN] = '\0';
+		if (r <= LINELEN)
+			srpline += r;
+		else
+			srpline += LINELEN;
+		fprintf(out, "%s\n", linebuffer);
+	}
+}
+
+int fill_rnd()
+{
+	int fd;
+	int fill = 0;
+
+	fd = open("/dev/urandom", O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	while (fill < 16) {
+		ssize_t r;
+		r = read(fd, rnd + fill, 16 - fill);
+		if (r < 0 && errno != EINTR && errno != EAGAIN) {
+			close(fd);
+			return 0;
+		} else if (r == 0) {
+			close(fd);
+			return 0;
+		} else {
+			fill += r;
+		}
+	}
+	close(fd);
+	return 1;
+}
+
+int main(int argc, char **argv)
+{
+	int idx, midx = 0;
+	char *username = NULL;
+	char *password = NULL;
+	char *password2 = NULL;
+	char *junk = NULL;
+	char *field = NULL;
+	char *file = NULL;
+	char *ans = NULL;
+	char *end = NULL;
+	FILE *filp = stdout;
+
+	if (argc > 1 && !strcmp(argv[1], "--help"))
+		do_help();
+
+username_again:
+	free(username);
+	username = prompt_string("Enter username", 0);
+	if (!*username) {
+		fprintf(stderr, "Error: Bad username\n");
+		goto username_again;
+	}
+
+	if (fill_rnd())
+		goto no_junk_prompt;
+junk_again:
+	free(junk);
+	junk = prompt_string("Enter some garbage from keyboard (min 32 "
+		"chars)", 1);
+	if (strlen(junk) < 32) {
+		fprintf(stderr, "Error: Garbage needs to be at least "
+			"32 characters\n");
+		goto junk_again;
+	}
+no_junk_prompt:
+
+passwords_again:
+	free(password);
+	free(password2);
+	password = prompt_string("Enter password", 1);
+	password2 = prompt_string("Enter password again", 1);
+	if (strcmp(password, password2)) {
+		fprintf(stderr, "Error: Passwords don't match\n");
+		goto passwords_again;
+	}
+
+field_again:
+	free(field);
+	for (midx = 0; fields[midx].f_name; midx++) {
+		printf("%i) %s\n", midx + 1, fields[midx].f_name);
+	}
+	field = prompt_string("Pick field", 0);
+	idx = (int)strtoul(field, &end, 10) - 1;
+	if (idx < 0 || idx >= midx || !*field || *end) {
+		printf("%i %i %i %i\n", idx, midx, *field, *end);
+		fprintf(stderr, "Error: Invalid choice\n");
+		goto field_again;
+	}
+
+file_again:
+	file = prompt_string("Filename to save as (enter for dump to "
+		"terminal)", 0);
+	if (*file) {
+		filp = fopen(file, "w");
+		if (!filp) {
+			fprintf(stderr, "Can't open \"%s\"\n", file);
+			goto file_again;
+		}
+	}
+
+	ans = generate_srp_line(username, password, junk, fields + idx);
+	flush_to_file(filp, ans);
+	if (filp != stdout)
+		fclose(filp);
+	return 0;
+}
diff --git a/git-over-tls/getkeyid.c b/git-over-tls/getkeyid.c
new file mode 100644
index 0000000..091e60b
--- /dev/null
+++ b/git-over-tls/getkeyid.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "home.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void do_help()
+{
+	printf("gits-get-key-name: Get name for keypair or hostkey.\n");
+	printf("Command line options:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("<keyfile>\n");
+	printf("\tRead key file <keyfile>. Name must contain at least\n");
+	printf("\tone '/'\n");
+	printf("<keyname>\n");
+	printf("\tRead key named <keyname>. Name must not contain\n");
+	printf("\t'/'\n");
+	printf("\n");
+	printf("Note: These key names are used in hostkey database and\n");
+	printf("as user names seen by authorization program.\n");
+	exit(0);
+}
+
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+
+int main(int argc, char **argv)
+{
+	struct certificate certificate;
+	gnutls_openpgp_crt_t cert;
+	char filename[PATH_MAX + 1];
+	unsigned char key[KEYBUF];
+	int s;
+	unsigned vout;
+
+	if (argc != 2) {
+		fprintf(stderr, "syntax: %s <keyname>\n", argv[0]);
+		fprintf(stderr, "syntax: %s <keyfile>\n", argv[0]);
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "--help"))
+		do_help();
+
+	if (strchr(argv[1], '/'))
+		s = snprintf(filename, PATH_MAX + 1, "%s", argv[1]);
+	else
+		s = snprintf(filename, PATH_MAX + 1, "%s/.gits/keys/%s",
+			get_home(), argv[1]);
+	if (s < 0 || s > PATH_MAX)
+		die("Insanely long homedir/keyname");
+
+	s = gnutls_global_init();
+	if (s < 0)
+		die("Can't initialize GnuTLS: %s",
+			gnutls_strerror(s));
+
+
+	certificate = parse_certificate(filename, &s);
+	if (s) {
+		if (s == CERTERR_NOCERT)
+			die("Can't find key %s", filename);
+		else if (s == CERTERR_CANTREAD)
+			die_errno("Can't read key");
+		else
+			die("Can't parse key: %s",
+				cert_parse_strerr(s));
+	}
+
+	s = gnutls_openpgp_crt_init(&cert);
+	if (s < 0)
+		die("Can't allocate space for key: %s",
+			gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_import(cert, &certificate.public_key,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+	if (vout)
+		die("Bad key: Validation failed");
+
+	vout = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	printf("openpgp-");
+	for (s = 0; s < (int)vout; s++)
+		printf("%02x", key[s]);
+	printf("\n");
+	return 0;
+}
diff --git a/git-over-tls/gits-send-special-command b/git-over-tls/gits-send-special-command
new file mode 100755
index 0000000..ade530d
--- /dev/null
+++ b/git-over-tls/gits-send-special-command
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (C) Ilari Liusvaara 2009
+#
+# This code is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+
+if test "x${1}" == "x--help"
+then
+	echo "gits-send-special-command: Send special command to "
+	echo "server"
+	echo "command line:"
+	echo "--help"
+	echo -e "\x09This help"
+	echo "<service> <URL>"
+	echo -e "\x09Send request for <service> to specified <URL>."
+	exit 0
+fi
+
+git-remote-gits --service=$1 $2
diff --git a/git-over-tls/home.c b/git-over-tls/home.c
new file mode 100644
index 0000000..b41dbfa
--- /dev/null
+++ b/git-over-tls/home.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+const char *get_home()
+{
+	static char *home = NULL;
+	const char *tmpans;
+#ifndef WIN32
+	struct passwd *user;
+#endif
+
+	if (home)
+		return home;
+
+	if (getenv("HOME")) {
+		tmpans = getenv("HOME");
+		goto got_it;
+	}
+
+#ifndef WIN32
+	user = getpwuid(getuid());
+	if (user && user->pw_dir) {
+		tmpans = user->pw_dir;
+		goto got_it;
+	}
+#endif
+
+	die("Can't obtain home directory of current user");
+got_it:
+	home = xstrdup(tmpans);
+	return home;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/home.h
new file mode 100644
index 0000000..133ee78
--- /dev/null
+++ b/git-over-tls/home.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _home__h__included__
+#define _home__h__included__
+
+const char *get_home();
+
+#endif
diff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c
new file mode 100644
index 0000000..28df0e5
--- /dev/null
+++ b/git-over-tls/hostkey.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "hostkey.h"
+#include "home.h"
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+#define MAXLINE 2048
+
+void check_hostkey(gnutls_session_t session, const char *hostname)
+{
+	const gnutls_datum_t *certificate = NULL;
+	unsigned int cert_size = 0;
+	gnutls_openpgp_crt_t cert;
+	int s;
+	unsigned int vout;
+	unsigned char key[KEYBUF];
+	FILE *hostfile;
+	char hostfilepath[PATH_MAX + 1];
+	char linebuffer[MAXLINE];
+
+	certificate = gnutls_certificate_get_peers(session, &cert_size);
+	if (!certificate)
+		die("Server didn't send a hostkey");
+
+	s = gnutls_openpgp_crt_init(&cert);
+	if (s < 0)
+		die("Can't allocate space for hostkey: %s",
+			gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_import(cert, certificate,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+	/* Defend against subkey attack. */
+	s = gnutls_openpgp_crt_get_subkey_count(cert);
+	if (s != 0)
+		die("Server sent bad hostkey: Subkeys are not allowed");
+
+	s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+	if (vout)
+		die("Server sent bad hostkey: Validation failed");
+
+	vout = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	s = snprintf(hostfilepath, PATH_MAX + 1, "%s/.gits/hostkeys",
+		get_home());
+	if (s < 0 || s > PATH_MAX)
+		die("Home directory path insanely long");
+
+	hostfile = fopen(hostfilepath, "r");
+	if (!hostfile)
+		die_errno("Can't open .gits/hostkeys");
+
+	while (fgets(linebuffer, MAXLINE - 2, hostfile)) {
+		char *split;
+		if (!*linebuffer || *linebuffer == '#')
+			continue;
+		if (linebuffer[strlen(linebuffer) - 1] == '\n')
+			linebuffer[strlen(linebuffer) - 1] = '\0';
+
+		split = strchr(linebuffer, ' ');
+		if (!split)
+			continue;
+		*split = '\0';
+		if (strcmp(linebuffer, hostname))
+			continue;
+
+		/*
+		 * Be nice to users and strip this in case it gets
+		 * retained from key id calculator.
+		 */
+		if (!strncmp(split + 1, "openpgp-", 8))
+			split += 8;
+
+		for (s = 0; s < vout; s++) {
+			char buffer[3];
+			sprintf(buffer, "%02x", (int)key[s]);
+			if (buffer[0] != split[2 * s + 1])
+				die("HOST KEY MISMATCH FOR HOST %s!",
+					hostname);
+			if (buffer[1] != split[2 * s + 2])
+				die("HOST KEY MISMATCH FOR HOST %s!",
+					hostname);
+		}
+		if (split[2 * vout + 1])
+			die("HOST KEY MISMATCH FOR HOST %s!",
+				hostname);
+		goto ok;
+	}
+	die("Hostkey for %s not found in hostkeys list", hostname);
+ok:
+	fclose(hostfile);
+}
diff --git a/git-over-tls/hostkey.h b/git-over-tls/hostkey.h
new file mode 100644
index 0000000..c0e7dfe
--- /dev/null
+++ b/git-over-tls/hostkey.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _hostkey__h__included__
+#define _hostkey__h__included__
+
+#include <gnutls/gnutls.h>
+
+void check_hostkey(gnutls_session_t session, const char *hostname);
+
+#endif
diff --git a/git-over-tls/hostkeymanager.c b/git-over-tls/hostkeymanager.c
new file mode 100644
index 0000000..2df09e0
--- /dev/null
+++ b/git-over-tls/hostkeymanager.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#define BUFSIZE 8192
+
+static void do_help()
+{
+	printf("gits-hostkey: Add, update or delete entries in hostkey.\n");
+	printf("database.\n");
+	printf("Command line:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("list\n");
+	printf("\tList hosts known and their keys\n");
+	printf("add <host> <key>\n");
+	printf("\tAdd <host> to database with key <key>\n");
+	printf("update <host> <key>\n");
+	printf("\tUpdate <host> to database to use key <key>\n");
+	printf("delete <host>\n");
+	printf("\tDelete key for <host>\n");
+	exit(0);
+}
+
+
+struct hostkey
+{
+	/* Hostname. NULL if not valid line. */
+	char *h_hostname;
+	/* Hostkey. NULL if not valid line. */
+	char *h_hostkey;
+	/* Line. Non-NULL if hostname/hoskey is NULL. */
+	char *h_line;
+	/* Next line. */
+	struct hostkey *h_next;
+};
+
+struct hostkey *first_hostkey = NULL;
+struct hostkey *last_hostkey = NULL;
+
+int ensure_leading_directories()
+{
+	struct stat s;
+	char fsobj[BUFSIZE];
+	int r;
+
+	sprintf(fsobj, "%s/.gits", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits");
+	else if (r == 0 && !S_ISDIR(s.st_mode))
+		die("$HOME/.gits exists but is not a directory");
+	else if (r < 0) {
+		/* Need to create it. */
+		if (mkdir(fsobj, 0700) < 0)
+			die_errno("Create $HOME/.gits failed");
+	}
+	/* Otherwise, r == 0 && S_ISDIR(s), which is OK. */
+	sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits/hostkeys");
+	else if (r == 0 && !S_ISREG(s.st_mode))
+		die("$HOME/.gits/hostkeys exists but is not a file");
+	/* Doesn't exist or a regular file. OK. */
+	return (r == 0) ? 1 : 0;
+}
+
+void load_hostkeys()
+{
+	char fsobj[BUFSIZE];
+	char linebuf[BUFSIZE];
+	FILE *filp;
+
+	if (!ensure_leading_directories())
+		return;
+
+	sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+	filp = fopen(fsobj, "r");
+	if (filp == NULL)
+		die("Can't open $HOME/.gits/hostkeys");
+
+	while (fgets(linebuf, BUFSIZE - 2, filp)) {
+		struct hostkey *h;
+		char *split;
+		h = xmalloc(sizeof(struct hostkey));
+		h->h_next = NULL;
+		h->h_line = xstrdup(linebuf);
+
+		if (*h->h_line && h->h_line[strlen(h->h_line) - 1] == '\n')
+			h->h_line[strlen(h->h_line) - 1] = '\0';
+
+		if (!*h->h_line || h->h_line[0] == '#')
+			goto not_valid;
+		split = strchr(h->h_line, ' ');
+		if (!split)
+			goto not_valid;
+
+		*split = '\0';
+		h->h_hostname = h->h_line;
+		h->h_hostkey = xstrdup(split + 1);
+		h->h_line = NULL;
+not_valid:
+		if (last_hostkey)
+			last_hostkey = last_hostkey->h_next = h;
+		else
+			first_hostkey = last_hostkey = h;
+	}
+	if (!feof(filp))
+		die("Error reading $HOME/.gits/hostkeys");
+	fclose(filp);
+}
+
+void save_hostkeys()
+{
+	char fsobj[BUFSIZE];
+	char fsobj2[BUFSIZE];
+	FILE *filp;
+	struct hostkey *host;
+
+	sprintf(fsobj, "%s/.gits/hostkeys.tmp", get_home());
+	sprintf(fsobj2, "%s/.gits/hostkeys", get_home());
+	filp = fopen(fsobj, "w");
+	if (filp == NULL)
+		die("Can't open $HOME/.gits/hostkeys.tmp");
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		if (host->h_line) {
+			if (fprintf(filp, "%s\n", host->h_line) < 0)
+				die("hostkeys write error");
+		} else {
+			if (fprintf(filp, "%s %s\n", host->h_hostname,
+				host->h_hostkey) < 0)
+				die("hostkeys write error");
+		}
+	}
+
+	if (fclose(filp) < 0)
+		die("Hostkeys write error");
+
+	if (rename(fsobj, fsobj2) < 0)
+		die("Error renaming hostkeys");
+}
+
+void list_hostkeys()
+{
+	struct hostkey *host;
+	size_t longest_hostname = 0;
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		size_t hostnamelen = 0;
+		hostnamelen = host->h_hostname ? strlen(host->h_hostname) : 0;
+		if (longest_hostname < hostnamelen)
+			longest_hostname = hostnamelen;
+	}
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		size_t pad;
+		size_t i;
+		if (!host->h_hostname)
+			continue;
+		pad = longest_hostname + 1 - strlen(host->h_hostname);
+		printf("%s", host->h_hostname);
+		for (i = 0; i < pad; i++)
+			printf(" ");
+		printf("%s\n", host->h_hostkey);
+	}
+}
+
+void add_hostkey(const char *host, const char *key)
+{
+	struct hostkey *h;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host))
+			die("Host %s already in hostkeys", host);
+	}
+
+	h = xmalloc(sizeof(struct hostkey));
+	h->h_next = NULL;
+	h->h_line = NULL;
+	h->h_hostname = xstrdup(host);
+	h->h_hostkey = xstrdup(key);
+
+	if (last_hostkey)
+		last_hostkey = last_hostkey->h_next = h;
+	else
+		first_hostkey = last_hostkey = h;
+}
+
+void update_hostkey(const char *host, const char *key)
+{
+	struct hostkey *h;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host)) {
+			h->h_hostkey = xstrdup(key);
+			return;
+		}
+	}
+	die("Host %s not found in hostkeys", host);
+}
+
+void delete_hostkey(const char *host)
+{
+	struct hostkey *h;
+	struct hostkey *prev = NULL;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host)) {
+			if (prev)
+				prev->h_next = h->h_next;
+			else
+				first_hostkey = h->h_next;
+			free(h);
+			return;
+		}
+		prev = h;
+	}
+	die("Host %s not found in hostkeys", host);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc < 2) {
+		fprintf(stderr, "syntax: %s list\n", argv[0]);
+		fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+		return 1;
+	}
+	if (!strcmp(argv[1], "--help"))
+		do_help();
+
+	if (!strcmp(argv[1], "list")) {
+		if (argc != 2) {
+			fprintf(stderr, "syntax: %s list\n", argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		list_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "add")) {
+		if (argc != 4) {
+			fprintf(stderr, "syntax: %s add <host> <key>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		add_hostkey(argv[2], argv[3]);
+		save_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "update")) {
+		if (argc != 4) {
+			fprintf(stderr, "syntax: %s update <host> <key>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		update_hostkey(argv[2], argv[3]);
+		save_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "delete")) {
+		if (argc != 3) {
+			fprintf(stderr, "syntax: %s delete <host>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		delete_hostkey(argv[2]);
+		save_hostkeys();
+		return 0;
+	} else {
+		fprintf(stderr, "syntax: %s list\n", argv[0]);
+		fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+		return 1;
+	}
+}
diff --git a/git-over-tls/keypairs.c b/git-over-tls/keypairs.c
new file mode 100644
index 0000000..cc77217
--- /dev/null
+++ b/git-over-tls/keypairs.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "keypairs.h"
+#include "home.h"
+#include "certificate.h"
+#include <stdio.h>
+#include <limits.h>
+#include <gnutls/openpgp.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+	const char *username)
+{
+	char keypath[PATH_MAX + 1];
+	const char *home;
+	struct certificate cert;
+	int r;
+
+	home = get_home();
+
+	r = snprintf(keypath, PATH_MAX + 1, "%s/.gits/keys/%s", home,
+		username);
+	if (r < 0 || r > PATH_MAX) {
+		die("Username too long");
+	}
+
+	cert = parse_certificate(keypath, &r);
+	if (r) {
+		if (r == CERTERR_NOCERT)
+			return -1;
+		if (r == CERTERR_CANTREAD)
+			die_errno("Can't read keypair");
+		else
+			die("Can't read keypair: %s",
+				cert_parse_strerr(r));
+	}
+
+	r = gnutls_certificate_set_openpgp_keyring_mem(creds,
+		cert.public_key.data, cert.public_key.size,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (r < 0)
+		die("Can't load public key: %s", gnutls_strerror(r));
+
+	r = gnutls_certificate_set_openpgp_key_mem(creds, &cert.public_key,
+		&cert.private_key, GNUTLS_OPENPGP_FMT_RAW);
+	if (r < 0)
+		die("Can't load keypair: %s", gnutls_strerror(r));
+
+	return 0;
+}
diff --git a/git-over-tls/keypairs.h b/git-over-tls/keypairs.h
new file mode 100644
index 0000000..11f4ef7
--- /dev/null
+++ b/git-over-tls/keypairs.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _keypairs__h__included__
+#define _keypairs__h__included__
+
+#include <gnutls/openpgp.h>
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+	const char *username);
+
+#endif
-- 
1.6.6.102.gd6f8f.dirty
Previous: Ilari LiusvaaraNext: Ilari Liusvaara
Message 2 of 28 in “[RFC 0/2] Git-over-TLS (gits://) client side support”
  1. Ilari LiusvaaraJan 13, 2010
  2. 1/2 Git-over-TLS (gits://) client side support (part 1 of 2)Ilari Liusvaara, Jan 13, 2010
  3. 2/2 Git-over-TLS (gits://) client side support (part 2 of 2)Ilari Liusvaara, Jan 13, 2010
  4. Alex RiesenJan 13, 2010
  5. Nguyen Thai Ngoc DuyJan 13, 2010
  6. Ilari LiusvaaraJan 13, 2010
  7. Andreas KreyJan 13, 2010
  8. Ilari LiusvaaraJan 13, 2010
  9. Andreas KreyJan 13, 2010
  10. Ilari LiusvaaraJan 13, 2010
  11. Andreas KreyJan 13, 2010
  12. Ilari LiusvaaraJan 13, 2010
  13. Avery PennarunJan 13, 2010
  14. Ilari LiusvaaraJan 13, 2010
  15. Avery PennarunJan 13, 2010
  16. Ilari LiusvaaraJan 13, 2010
  17. Avery PennarunJan 13, 2010
  18. Shawn O. PearceJan 13, 2010
  19. Ilari LiusvaaraJan 13, 2010
  20. Avery PennarunJan 13, 2010
  21. Ilari LiusvaaraJan 14, 2010
  22. Avery PennarunJan 14, 2010
  23. Ilari LiusvaaraJan 14, 2010
  24. Andreas KreyJan 13, 2010
  25. Ilari LiusvaaraJan 13, 2010
  26. Avery PennarunJan 13, 2010
  27. Ilari LiusvaaraJan 13, 2010
  28. Edward Z. YangJan 13, 2010

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.