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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Ilari Liusvaara <ilari.liusvaara@xxxxxxxxxxx>
---
 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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]