[PATCH 1/3] git over TLS (gits://) support (part 1)

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

 



Signed-off-by: Ilari Liusvaara <ilari.liusvaara@xxxxxxxxxxx>
---
 Makefile               |   25 +-
 git-over-tls/Makefile  |   45 ++
 git-over-tls/cbuffer.c |  504 +++++++++++++++
 git-over-tls/cbuffer.h |  304 +++++++++
 git-over-tls/user.c    | 1597 ++++++++++++++++++++++++++++++++++++++++++++++++
 git-over-tls/user.h    |  367 +++++++++++
 6 files changed, 2839 insertions(+), 3 deletions(-)
 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/user.c
 create mode 100644 git-over-tls/user.h

diff --git a/Makefile b/Makefile
index 910f471..b11fed4 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'.
@@ -295,7 +298,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..
@@ -1284,6 +1287,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
@@ -1433,6 +1439,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)
@@ -1456,7 +1466,7 @@ endif
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
-export TAR INSTALL DESTDIR SHELL_PATH
+export TAR INSTALL DESTDIR SHELL_PATH ALL_CFLAGS
 
 
 ### Build rules
@@ -1476,6 +1486,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
@@ -1969,6 +1982,9 @@ install: all
 	$(INSTALL) -m 644 $(SCRIPT_LIB) '$(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
@@ -2101,6 +2117,9 @@ ifndef NO_PERL
 	$(RM) gitweb/gitweb.cgi gitweb/gitweb.min.*
 	$(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/Makefile b/git-over-tls/Makefile
new file mode 100644
index 0000000..69d1d33
--- /dev/null
+++ b/git-over-tls/Makefile
@@ -0,0 +1,45 @@
+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))
+helper = git-remote-gits
+programs = gits-get-key-id gits-generate-keypair
+scripts = gits-send-special-command gits-send-special-command-nourl
+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
+
+ssh_deps = ssh.o ssh_localkey.o pem.o base64.o home.o pem_decrypt.o srp_askpass.o prompt.o user.o cbuffer.o misc.o
+
+all: $(programs) $(helper)
+
+git-remote-gits: main.o user.o cbuffer.o keypairs.o hostkey.o certificate.o connect.o $(ssh_deps)
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-get-key-id: getkeyid.o certificate.o $(ssh_deps)
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+ifndef NO_SRP
+gits-generate-srp-verifier: gensrpverifier.o prompt.o home.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+endif
+
+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) $(ALL_CFLAGS) -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/user.c b/git-over-tls/user.c
new file mode 100644
index 0000000..bcfeac1
--- /dev/null
+++ b/git-over-tls/user.c
@@ -0,0 +1,1597 @@
+/*
+ * 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 "user.h"
+#include "cbuffer.h"
+#include "misc.h"
+#include <sys/select.h>
+#include <sys/time.h>
+#include <gnutls/gnutls.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#ifdef USE_TRAP_PAGING
+#include <sys/mman.h>
+#endif
+
+/*
+ * 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.
+ */
+
+/* Main buffer size. */
+#define BUFFERSIZE 65536
+/*
+ * Error buffer can be maximum of 65535-8 bytes and must be smaller or
+ * equal in size to main buffers.
+ */
+#define ERR_BUFFERSIZE (65535-8)
+
+struct user
+{
+	/* If 1, EOF received from black in at transport level. */
+	unsigned u_black_in_eof : 1,
+	/* If 1, EOF sent to black out at transport level. */
+		u_black_out_eof : 1,
+	/* If 1, EOF received from black in at TLS level. */
+		u_black_in_d_eof : 1,
+	/* If 1, EOF sent to black out at TLS level. */
+		u_black_out_d_eof : 1,
+	/* If 1, Assume that there is more input to come from red in. */
+		u_red_assume_more : 1,
+	/* If 1, TLS is active and ready to transfer data. */
+		u_tls_active : 1,
+	/* If 1, there has been data received from red in. */
+		u_red_in_have_data : 1,
+	/*
+	 * Result of last read of decrypted data.
+	 * 0 => Read was successful.
+	 * 1 => Read was blocked by insufficient input data.
+	 * 2 => Read was blocked by insufficient output space.
+	 * 3 => No read attempted.
+	 */
+		u_want_read : 2,
+	/*
+	 * Result of last write of decrypted data.
+	 * 0 => Write was successful.
+	 * 1 => Write was blocked by insufficient input data.
+	 * 2 => Write was blocked by insufficient output space.
+	 * 3 => No write attempted.
+	 */
+		u_want_write : 2,
+	/*
+	 * Result of last handshake attempt.
+	 * 0 => Handshake was successful.
+	 * 1 => Handshake was blocked by insufficient input data.
+	 * 2 => Handshake was blocked by insufficient output space.
+	 * 3 => No handshake attempted.
+	 */
+		u_want_hand : 2,
+	/* Number of bytes of error header sent (0-8). */
+		u_red_err_hdr_sent : 4,
+	/* Delay TLS failure present. 1 for handshake, 2 otherwise */
+		u_delay_tls_failure : 2,
+	/* Has seen any data received. */
+		u_seen_input_data : 1;
+
+	/* File descriptors. -1 if none. */
+	int u_black_fd;		/* Input/Output */
+	int u_red_in_fd;	/* Input */
+	int u_red_out_fd;	/* Output */
+	int u_red_err_fd;	/* Input */
+	/* The backing buffer for all transfer buffers and its size. */
+	unsigned char *u_buf_backing;
+	size_t u_buf_backing_size;
+	/* The actual transfer buffers. */
+	struct cbuffer *u_black_in_buf;
+	struct cbuffer *u_black_out_buf;
+	struct cbuffer *u_red_in_buf;
+	struct cbuffer *u_red_out_buf;
+	struct cbuffer *u_red_err_buf;
+	/* Deadline. Tv_sec is -1 if there is no deadline. */
+	struct timeval u_deadline;
+	/*
+	 * Why the connection was torn down. Delay_failure is delayed
+	 * failure that becomes real after output buffer is flushed
+	 * so that TLS alerts are sent. Also stored is delayed TLS alert
+	 * that couldn't be sent yet.
+	 */
+	int u_failure;
+	char *u_errmsg;
+	int u_delay_failure;
+	gnutls_alert_description_t u_delay_alert;
+	/* Active TLS session. NULL if no TLS. */
+	gnutls_session_t u_tls_session;
+	/* Transfer counters. */
+	uint64_t u_raw_tx;
+	uint64_t u_raw_rx;
+	uint64_t u_decoded_tx;
+	uint64_t u_decoded_rx;
+	struct timeval u_last_rx;
+	struct timeval u_last_tx;
+};
+
+/* Initiate delayed failure on user. */
+static void delay_cleanup_user(struct user *user, int failure,
+	const char *error)
+{
+	/* Store the cause of termination. */
+	if (!user->u_errmsg) {
+		if (error)
+			user->u_errmsg = strdup(error);
+		else
+			user->u_errmsg = NULL;
+	}
+	user->u_delay_failure = failure;
+}
+
+/* Clean up user connection immediately. */
+static void cleanup_user(struct user *user, int failure, const char *error)
+{
+	/* If there is TLS session, deallocate its resources. */
+	if (user->u_tls_session) {
+		gnutls_deinit(user->u_tls_session);
+		user->u_tls_session = NULL;
+	}
+	/* Shutdown and close black input/output. */
+	if (user->u_black_fd >= 0) {
+		shutdown(user->u_black_fd, SHUT_WR);
+		force_close(user->u_black_fd);
+	}
+	user->u_black_fd = -1;
+
+	/* Close red inputs/outputs. */
+	if (user->u_red_in_fd >= 0)
+		force_close(user->u_red_in_fd);
+	user->u_red_in_fd = -1;
+	if (user->u_red_out_fd >= 0)
+		force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+	if (user->u_red_err_fd >= 0)
+		force_close(user->u_red_err_fd);
+	user->u_red_err_fd = -1;
+
+	/* Store the cause of termination. */
+	if (!user->u_errmsg) {
+		if (error)
+			user->u_errmsg = strdup(error);
+		else
+			user->u_errmsg = NULL;
+	}
+	user->u_failure = failure;
+}
+
+/*
+ * Process delay failure. If there is delayed failure and output buffer is
+ * empty, make it real failure and disconnect user immediately.
+ */
+static int process_delay_failure(struct user *user)
+{
+	if (user->u_failure)
+		return 0;
+	if (!cbuffer_used(user->u_black_out_buf) && user->u_delay_failure) {
+		cleanup_user(user, user->u_delay_failure, NULL);
+		return 0;
+	}
+	return 0;
+}
+
+/* Is this error from I/O syscall fatal? */
+static int is_fatal_error(int error)
+{
+	return (error != EINTR && error != EAGAIN && error != EWOULDBLOCK);
+}
+
+/* Is this error from GnuTLS I/O operation fatal? */
+static int is_fatal_tls_error(int error)
+{
+	/*
+	 * GNUTLS_E_INTERRUPTED should never be seen. since custom push and
+	 * pull functions can't return EINTR, only EAGAIN.
+	 */
+	return (error < 0 && error != GNUTLS_E_AGAIN);
+}
+
+/* Handle fatal error received from GnuTLS functions. */
+static int handle_tls_failure_code(struct user *user, int gnutls_code,
+	int handshaking)
+{
+	int x;
+	char error_buffer[8192];
+	if (gnutls_code == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+		/* Fatal alert. Get the description and format message. */
+		gnutls_alert_description_t alert;
+		alert = gnutls_alert_get(user->u_tls_session);
+		sprintf(error_buffer, "TLS alert received: %s",
+			gnutls_alert_get_name(alert));
+
+		if (alert == GNUTLS_A_BAD_RECORD_MAC && handshaking)
+			sprintf(error_buffer, "TLS alert received: %s "
+				"(incorrect password?)",
+				gnutls_alert_get_name(alert));
+
+		/* Terminate the connection. */
+		if (handshaking)
+			cleanup_user(user, USER_TLS_HAND_ERROR,
+				error_buffer);
+		else
+			cleanup_user(user, USER_TLS_ERROR,
+				error_buffer);
+		return 0;
+	}
+	/*
+	 * Alerts use handshake readyness indicator, so try to set it
+	 * to "perform I/O immediately".
+	 */
+	user->u_want_hand = 0;
+	/*
+	 * Mark that delay TLS failure is present and get the alert
+	 * that should be sent. Also set error message.
+	 */
+	user->u_delay_tls_failure = 1;
+#ifndef DISABLE_SRP
+	/* GnuTLS doesn't seem to have proper code for this. */
+	if (gnutls_code == GNUTLS_E_SRP_PWD_ERROR)
+		user->u_delay_alert = GNUTLS_A_UNKNOWN_PSK_IDENTITY;
+	else
+#endif
+		user->u_delay_alert = (gnutls_alert_description_t)
+			gnutls_error_to_alert((int)gnutls_code, &x);
+	user->u_errmsg = strdup(gnutls_strerror_name((int)gnutls_code));
+	return 1;
+}
+
+/* Handle black input activity */
+static int black_in_handler(struct user *user, fd_set *rfds)
+{
+	/* Don't attempt to read if connection has failed. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* The file descrptor must be marked readable. */
+	if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, rfds))
+		return 0;
+	/* Don't attempt to read already EOF'd file descriptor. */
+	if (user->u_black_in_eof)
+		return 0;
+
+	ssize_t r = cbuffer_read_fd(user->u_black_in_buf, user->u_black_fd);
+	FD_CLR(user->u_black_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound connection faulted! */
+		cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+	} else if (r == 0) {
+		/* Received EOF. If TLS is enabled, also assume output EOF. */
+		user->u_black_in_eof = 1;
+		if (user->u_tls_session)
+			user->u_black_out_eof = 1;
+	} else if (r > 0) {
+		/* Received some data. */
+		user->u_raw_rx += r;
+		user->u_seen_input_data = 1;
+		gettimeofday(&user->u_last_rx, NULL);
+	}
+	return 1;
+}
+
+/* Handle black output activity. */
+static int black_out_handler(struct user *user, fd_set *wfds)
+{
+	/*
+	 * Don't attempt to write if connection has failed.  This is one of
+	 * the very few handlers still to run in delayed failure.
+	 */
+	if (user->u_failure)
+		return 0;
+	/* The file descrptor must be marked as writable. */
+	if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, wfds))
+		return 0;
+	/* Don't attempt to write if EOF has already been sent. */
+	if (user->u_black_out_eof)
+		return 0;
+
+	ssize_t r = cbuffer_write_fd(user->u_black_out_buf, user->u_black_fd);
+	FD_CLR(user->u_black_fd, wfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Outbound connection faulted! */
+		cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+	} else if (r > 0) {
+		/* Sent some data. */
+		user->u_raw_tx += r;
+		gettimeofday(&user->u_last_tx, NULL);
+	}
+	return 1;
+}
+
+/* Send black out EOF if needed (using TLS if it is active) */
+static int black_out_eof_handler(struct user *user)
+{
+	/* Don't operate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate anymore if EOF has been sent outbound. */
+	if (user->u_black_out_eof || user->u_black_out_d_eof)
+		return 0;
+	/* Don't activate if there can be more data from red in. */
+	if (user->u_red_assume_more || user->u_red_in_fd >= 0)
+		return 0;
+	/* Don't activate if there can be more data from red error. */
+	if (user->u_red_err_fd >= 0)
+		return 0;
+	/* Don't activate if there is more data in red input. */
+	if (cbuffer_used(user->u_red_in_buf))
+		return 0;
+	/* Don't activate if there is more data in red error. */
+	if (cbuffer_used(user->u_red_err_buf))
+		return 0;
+	/*
+	 * If not in TLS mode, don't activate if there is data in send
+	 * buffer.
+	 */
+	if (!user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+		return 0;
+
+	if (user->u_tls_session) {
+		int r;
+		/* Try to send the EOF at TLS level. */
+		r = gnutls_bye(user->u_tls_session, GNUTLS_SHUT_WR);
+		if (r == 0) {
+			/* Suceeded. Mark the connection as EOF'd. */
+			user->u_black_out_d_eof = 1;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal stream error! */
+			return handle_tls_failure_code(user, (int)r, 0);
+		}
+		/* Otherwise we failed due to non-fatal TLS error. We'll try
+		   again soon. */
+		return 0;
+	} else {
+		/* Try to send normal transport EOF. */
+		if (shutdown(user->u_black_fd, SHUT_WR) < 0) {
+			/* Failed, declare as stream error. */
+			cleanup_user(user, USER_LAYER4_ERROR,
+				strerror(errno));
+			return 0;
+		}
+		user->u_black_out_eof = 1;
+		return 1;
+	}
+	return 0; /* Should not be here. */
+}
+
+/* Send EOF to red output if needed. */
+static int red_out_eof_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate unless input EOF has been received. */
+	if (!user->u_black_in_eof && !user->u_black_in_d_eof)
+		return 0;
+	/* Don't activate anymore if red out has been closed. */
+	if (user->u_red_out_fd < 0)
+		return 0;
+	/* If not in TLS mode, don't activate if there is data in receive
+	   buffer. */
+	if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+		return 0;
+	/* Don't activate if there is more data in red output. */
+	if (cbuffer_used(user->u_red_out_buf))
+		return 0;
+
+	force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+	return 1;
+}
+
+/* Send data to file descriptor connected to red output. */
+static int red_out_handler(struct user *user, fd_set *wfds)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red out must be writable fd. */
+	if (user->u_red_out_fd < 0 || !FD_ISSET(user->u_red_out_fd, wfds))
+		return 0;
+
+	ssize_t r = cbuffer_write_fd(user->u_red_out_buf, user->u_red_out_fd);
+	FD_CLR(user->u_red_out_fd, wfds);
+	if (r < 0 && errno == EPIPE) {
+		/* EPIPE is treated as special type of EOF. We need to read
+		   EOFs from process. */
+		force_close(user->u_red_out_fd);
+		user->u_red_out_fd = -1;
+		return 1;
+	} else if (r < 0 && is_fatal_error(errno)) {
+		/* Red out connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+		return 0;
+	} else if (r > 0) {
+		/* Sent some data to red output. */
+		return 1;
+	}
+	/* Can't come here. */
+	return 0;
+}
+
+/* Recieve data from file descriptor connected to red input. */
+static int red_in_handler(struct user *user, fd_set *rfds)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red in must be readable fd. */
+	if (user->u_red_in_fd < 0 || !FD_ISSET(user->u_red_in_fd, rfds))
+		return 0;
+
+	ssize_t r = cbuffer_read_fd(user->u_red_in_buf, user->u_red_in_fd);
+	FD_CLR(user->u_red_in_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound red connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+		return 0;
+	} else if (r == 0) {
+		/* Close it so we eventually send EOF. */
+		user->u_red_assume_more = 0;
+		force_close(user->u_red_in_fd);
+		user->u_red_in_fd = -1;
+		return 1;
+	} else if (r > 0) {
+		/* Received some data. */
+		user->u_red_in_have_data = 1;
+
+		/* Clear the error buffer. */
+		cbuffer_clear(user->u_red_err_buf);
+	}
+	return 1;
+}
+
+#define DUMMYBUFSIZE 256
+
+/* Receive data from file descriptor connected to red error. */
+static int red_err_handler(struct user *user, fd_set *rfds)
+{
+	ssize_t r;
+	char buf[DUMMYBUFSIZE];
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red err must be readable fd. */
+	if (user->u_red_err_fd < 0 || !FD_ISSET(user->u_red_err_fd, rfds))
+		return 0;
+
+	if (!user->u_red_in_have_data) {
+		/* Read the red error for real. */
+		r = cbuffer_read_fd(user->u_red_err_buf, user->u_red_err_fd);
+	} else {
+		/* Just do dummy read and discard the reuslts. */
+		r = read(user->u_red_err_fd, buf, DUMMYBUFSIZE);
+	}
+	FD_CLR(user->u_red_err_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound red connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+	} else if (r == 0) {
+		/* Close it so we eventually send EOF. */
+		force_close(user->u_red_err_fd);
+		user->u_red_err_fd = -1;
+	} else if (r > 0) {
+		/*
+		 * Received some data.
+		 *
+		 * HACK ALERT: Some systems seem to like to send these
+		 * non-errors on error channel. Ignore those and treat
+		 * them like data from stdout. (35 is length of that
+		 * string).
+		 */
+		const char *tmsg = "Initialized empty Git repository in";
+		unsigned char tmp[35];
+		if (cbuffer_peek(user->u_red_err_buf, tmp, 35) >= 0 &&
+			!strncmp(tmsg, (char*)tmp, 35)) {
+			user->u_red_in_have_data = 1;
+			cbuffer_clear(user->u_red_err_buf);
+		}
+	}
+	return 1;
+}
+
+/* Terminate the whole connection if needed EOFs have been seen. */
+static int connection_eof_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if red out is still open. */
+	if (user->u_red_out_fd >= 0)
+		return 0;
+	/* Don't activate unless output EOF has been asserted. */
+	if (!user->u_black_out_eof && !user->u_black_out_d_eof)
+		return 0;
+	/* Don't assert in TLS mode unless output buffer is empty. */
+	if (user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+		return 0;
+
+	/* Both half-connections have ended. End the entiere connection. */
+	cleanup_user(user, USER_CONNECTION_END, NULL);
+	return 1;
+}
+
+/*
+ * Should operation take place given last operation failed practicular way?
+ * Direction2 is 0 if last operation didn't fail, 1 if it failed due to
+ * insufficient data to read, 2 if it failed due to insufficient space to
+ * write.
+ */
+static int gnutls_blacks_activate2(struct user *user, int direction2)
+{
+	/*
+	 * If not attempted yet, or last time it was successful, attempt
+	 * immediately again.
+	 */
+	if (direction2 == 0 || direction2 == 3)
+		return 1;
+	/*
+	 * If last time it failed due to insufficient read space, try
+	 * again only if there is some data in read buffers now.
+	 */
+	if (direction2 == 1) {
+		if (cbuffer_used(user->u_black_in_buf) != 0)
+			return 1;
+		else if (!user->u_black_in_eof)
+			return 0;
+		else if (user->u_tls_session && user->u_seen_input_data)
+			cleanup_user(user, USER_TLS_ERROR, "Connection "
+				"closed unexpectedly");
+		else if (user->u_tls_session)
+			cleanup_user(user, USER_LAYER4_ERROR, "Connection "
+				"broke before any data was received");
+		else
+			return 0;
+	}
+	/*
+	 * If last time it failed due to insufficient write space, try
+	 * again only if there is some space in write buffers now.
+	 */
+	if (direction2 == 2)
+		return (cbuffer_free(user->u_black_out_buf) != 0);
+	return 0;
+}
+
+/* Copy or decrypt data from black input to red output. */
+static int black_to_red_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if there's TLS but it isn't ready yet. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Don't activate if there's no space in red out buffer. */
+	if (!cbuffer_free(user->u_red_out_buf))
+		return 0;
+	/* Don't activate if TLS-level EOF has been received. */
+	if (user->u_black_in_d_eof)
+		return 0;
+	/* Check that we don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_read))
+		return 0;
+
+	if (!user->u_tls_session) {
+		/*
+		 * no-TLS case. Just compute how much data we can move and
+		 * then just move it from black in buffer to red out buffer.
+		 */
+		size_t amount;
+		amount = cbuffer_move_nolimit(user->u_red_out_buf,
+			user->u_black_in_buf);
+		if (amount > 0) {
+			/* Ok, some data to move. Just move it. */
+			user->u_want_read = 0;
+			user->u_decoded_rx += amount;
+			return 1;
+		} else {
+			/*
+			 * No data to transfer. Mark operation failed due to
+			 * insufficient data to read.
+			 */
+			user->u_want_read = 1;
+			return 0;
+		}
+	} else {
+		unsigned char *ptr;
+		size_t size;
+		ssize_t r;
+
+		/*
+		 * Compute the segment for writing data to. And if we do get
+		 * such segment, receive data to it. The size == 0 case should
+		 * not happen because we checked that there's space in red
+		 * out buffer above.
+		 */
+		cbuffer_fill_w_segment(user->u_red_out_buf, &ptr, &size);
+		r = gnutls_record_recv(user->u_tls_session, ptr, size);
+		if (r > 0) {
+			/* Received TLS data. */
+			cbuffer_commit_w_segment(user->u_red_out_buf, r);
+			user->u_decoded_rx += r;
+			user->u_want_read = 0;
+			return 1;
+		} else if (r == 0) {
+			/* Received TLS EOF. */
+			user->u_black_in_d_eof = 1;
+			user->u_want_read = 0;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary read failure. */
+			user->u_want_read = 1 + gnutls_record_get_direction(
+				user->u_tls_session);
+			return 0;
+		}
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/* Copy or encrypt data from red input to black output. */
+static int red_to_black_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is present but not active. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Require data in red in buffer. */
+	if (!cbuffer_used(user->u_red_in_buf))
+		return 0;
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_write))
+		return 0;
+
+	if (!user->u_tls_session) {
+		/*
+		 * No-TLS case. Just find maximum amount of data to move and
+		 * then move the data.
+		 */
+		size_t amount;
+		amount = cbuffer_move_nolimit(user->u_black_out_buf,
+			user->u_red_in_buf);
+		if (amount > 0) {
+			/* Ok, some data to move. Just move it. */
+			user->u_want_write = 0;
+			user->u_decoded_tx += amount;
+			return 1;
+		} else {
+			/*
+			 * No data to transfer. Mark operation failed due to
+			 * insufficient space to write.
+			 */
+			user->u_want_write = 2;
+			return 0;
+		}
+	} else {
+		unsigned char *ptr;
+		size_t size;
+		ssize_t r = 0;
+
+		/*
+		 * Compute the segment for reading data to. And if we do get
+		 * such segment, send data from it. The size == 0 case should
+		 * not happen because we checked that there's data in red
+		 * in buffer above.
+		 */
+		cbuffer_fill_r_segment(user->u_red_in_buf, &ptr, &size);
+		if (user->u_want_write == 0 || user->u_want_write == 3)
+			/* Last was success, just send new record. */
+			r = gnutls_record_send(user->u_tls_session, ptr,
+				size);
+		else
+			/* Last time it failed, try to resend the record. */
+			r = gnutls_record_send(user->u_tls_session, NULL, 0);
+		if (r > 0) {
+			/* Sent some TLS data. */
+			cbuffer_commit_r_segment(user->u_red_in_buf, r);
+			user->u_decoded_tx += r;
+			user->u_want_write = 0;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary send failure. */
+			user->u_want_write = 1 + gnutls_record_get_direction(user->u_tls_session);
+			return 0;
+		}
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+static const char hexes[] = "0123456789abcdef";
+
+#define TMPBUFSIZE 256
+
+/* Copy or encrypt data from red error to black out. */
+static int rederr_to_black_handler(struct user *user)
+{
+	unsigned char hdrbuf[8];
+	unsigned char buffer[TMPBUFSIZE];
+	size_t bufusage = 0;
+	size_t pcktsize;
+	unsigned char *segstart;
+	size_t seglen;
+	size_t maxread;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is not yet ready. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Don't activate if there's no data in red error. */
+	if (!cbuffer_used(user->u_red_err_buf))
+		return 0;
+	/* Don't activate if red in hasn't been closed. */
+	if (user->u_red_in_fd >= 0)
+		return 0;
+	/* Don't activate if red error can have more data. */
+	if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf))
+		return 0;
+	/* Don't fail like last write. */
+	if (!gnutls_blacks_activate2(user, user->u_want_write))
+		return 0;
+
+	/* Safety hatch. Truncate error if too long. */
+	if (!cbuffer_free(user->u_red_err_buf) && user->u_red_err_fd >= 0) {
+		force_close(user->u_red_err_fd);
+		user->u_red_err_fd = -1;
+	}
+
+	/* Fill the header. */
+	pcktsize = 8 + cbuffer_used(user->u_red_err_buf);
+	hdrbuf[0] = hexes[pcktsize / 4096 % 16];
+	hdrbuf[1] = hexes[pcktsize / 256 % 16];
+	hdrbuf[2] = hexes[pcktsize / 16 % 16];
+	hdrbuf[3] = hexes[pcktsize % 16];
+	hdrbuf[4] = 'E';
+	hdrbuf[5] = 'R';
+	hdrbuf[6] = 'R';
+	hdrbuf[7] = ' ';
+	/*
+	 * If header hasn't been sent yet, copy the remainder of header to
+	 * transfer buffer.
+	 */
+	if (user->u_red_err_hdr_sent < 8) {
+		memcpy(buffer, hdrbuf + user->u_red_err_hdr_sent,
+			8 - user->u_red_err_hdr_sent);
+		bufusage = (8 - user->u_red_err_hdr_sent);
+	}
+	maxread = TMPBUFSIZE - bufusage;
+
+	/*
+	 * Request read segment out of red error. There's always data in
+	 * red error buffer by checks above. Then copy as much data from
+	 * it as possible.
+	 */
+	cbuffer_fill_r_segment(user->u_red_err_buf, &segstart, &seglen);
+	if (maxread >= seglen) {
+		memcpy(buffer + bufusage, segstart, seglen);
+		bufusage += seglen;
+	} else {
+		memcpy(buffer + bufusage, segstart, maxread);
+		bufusage += maxread;
+	}
+
+	/*
+	 * Bufusage is always positive, since its either equal to seglen
+	 * or maxread, and neither can be zero.
+	 */
+	if (!user->u_tls_session && !user->u_red_in_have_data) {
+		/*
+		 * Compute maximum amount of data that can be copied to
+		 * send buffer (no TLS case).
+		 */
+		bufusage = cbuffer_write_max(user->u_black_out_buf, buffer,
+			bufusage);
+		if (bufusage > 0) {
+			user->u_want_write = 0;
+			user->u_decoded_tx += bufusage;
+		} else {
+			/* No space, mark it failed due to write. */
+			user->u_want_write = 2;
+			return 0;
+		}
+	} else if (!user->u_red_in_have_data) {
+		ssize_t r;
+
+		if (user->u_want_write == 0 || user->u_want_write == 3)
+			/* Last was success, just send new record. */
+			r = gnutls_record_send(user->u_tls_session, buffer,
+				bufusage);
+		else
+			/* Last time it failed, try to resend the record. */
+			r = gnutls_record_send(user->u_tls_session, NULL, 0);
+		if (r > 0) {
+			/* Successfully sent data. Adjust bufusage. */
+			bufusage = r;
+			user->u_decoded_tx += bufusage;
+			user->u_want_write = 0;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary failure. Mark the failure. */
+			user->u_want_write = 1 + gnutls_record_get_direction(
+				user->u_tls_session);
+			bufusage = 0;
+			return 0;
+		}
+	}
+
+	/* Now bufusage is set to amount of bytes sent. ACK the data sent. */
+	if ((size_t)user->u_red_err_hdr_sent + bufusage < 8) {
+		/* Partially sent header. ACK the header sent. */
+		user->u_red_err_hdr_sent += bufusage;
+		bufusage = 0;
+		return 1;
+	} else {
+		/*
+		 * Completely sent the header. ACK rest of it and substract
+		 * it from actual data sent.
+		 */
+		bufusage -= (8 - user->u_red_err_hdr_sent);
+		user->u_red_err_hdr_sent = 8;
+	}
+	/* ACK the actual error data sent. */
+	if (bufusage > 0)
+		cbuffer_commit_r_segment(user->u_red_err_buf, bufusage);
+
+	return 1;
+}
+
+/* Try to send delayed alert (No-TLS case). */
+static int tls_alert_handler_no_tls(struct user *user)
+{
+	//Count it as immediates success.
+	cleanup_user(user, USER_KILL, "Would send fatal alert");
+	return 0;
+}
+
+/* Try to send delayed alert. */
+static int tls_alert_handler(struct user *user)
+{
+	int r;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't do this without delayed tls failure. */
+	if (!user->u_delay_tls_failure)
+		return 0;
+	/* Handle no-TLS case. */
+	if (!user->u_tls_session)
+		return tls_alert_handler_no_tls(user);
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_hand))
+		return 0;
+
+	r = gnutls_alert_send(user->u_tls_session, GNUTLS_AL_FATAL,
+		user->u_delay_alert);
+	if (r == 0) {
+		/* The user->u_delay_tls_failure may be 2 too. */
+		if (user->u_delay_tls_failure == 1)
+			delay_cleanup_user(user, USER_TLS_HAND_ERROR,
+				"Fatal alert sent");
+		else
+			delay_cleanup_user(user, USER_TLS_ERROR,
+				"Fatal alert sent");
+		return 0;
+	} else if (is_fatal_tls_error(r)) {
+		const char *err = gnutls_strerror_name((int)r);
+		delay_cleanup_user(user, USER_TLS_ERROR, err);
+		return 1;
+	} else if (r < 0) {
+		/* Still needs more attempts to send. */
+		user->u_want_hand = 1 + gnutls_record_get_direction(
+			user->u_tls_session);
+		return 0;
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/* Try to handshake TLS connection. */
+static int handshake_handler(struct user *user)
+{
+	int r;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is ready or no TLS. */
+	if (user->u_tls_active || !user->u_tls_session)
+		return 0;
+	/* Don't handshake anoymore with delayed TLS failure. */
+	if (user->u_delay_tls_failure)
+		return 0;
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_hand))
+		return 0;
+
+	r = gnutls_handshake(user->u_tls_session);
+	if (r == 0) {
+		/* Handshake completed, TLS ready. */
+		user->u_want_hand = 0;
+		user->u_tls_active = 1;
+		return 1;
+	} else if (is_fatal_tls_error(r)) {
+		/* Fatal TLS error. */
+		return handle_tls_failure_code(user, (int)r, 1);
+	} else if (r < 0) {
+		/* Still needs more handshaking. */
+		user->u_want_hand = 1 + gnutls_record_get_direction(
+			user->u_tls_session);
+		return 0;
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/*
+ * Compare two timevals. Returns -1 if first is first, 0 if same, 1 otherwise
+ */
+static int tv_compare(const struct timeval *tv1, const struct timeval *tv2)
+{
+	if (tv1->tv_sec == -1)
+		return (tv2->tv_sec == -1) ? 0 : 1;
+	if (tv2->tv_sec == -1)
+		return -1;
+	if (tv1->tv_sec > tv2->tv_sec)
+		return 1;
+	if (tv1->tv_sec < tv2->tv_sec)
+		return -1;
+	if (tv1->tv_usec > tv2->tv_usec)
+		return 1;
+	if (tv1->tv_usec < tv2->tv_usec)
+		return -1;
+	return 0;
+}
+
+/* Handle timeouts on connection. */
+static int timeout_handler(struct user *user)
+{
+	struct timeval t;
+
+	gettimeofday(&t, NULL);
+
+	/* Don't activate on already failed connection. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Is it time to activate? */
+	if (tv_compare(&user->u_deadline, &t) >= 0)
+		return 0;
+
+	cleanup_user(user, USER_TIMEOUT, "Request timeout");
+	return 0;
+}
+
+/* GnuTLS push function. */
+static ssize_t gnutls_push_data(gnutls_transport_ptr_t ptr, const void *data,
+	size_t size)
+{
+	struct user *user = (struct user*)ptr;
+
+	/* Compute maximum transfer. */
+	if (size > cbuffer_free(user->u_black_out_buf))
+		size = cbuffer_free(user->u_black_out_buf);
+
+	/* If no data can be read, send EAGAIN. */
+	if (size == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	cbuffer_write(user->u_black_out_buf, (unsigned char*)data, size);
+	return size;
+}
+
+/* GnuTLS pull function. */
+static ssize_t gnutls_pull_data(gnutls_transport_ptr_t ptr, void *data,
+	size_t size)
+{
+	struct user *user = (struct user*)ptr;
+
+	/* Compute maximum transfer. */
+	if (size > cbuffer_used(user->u_black_in_buf))
+		size = cbuffer_used(user->u_black_in_buf);
+
+	/* If no data can be written, send EAGAIN. */
+	if (size == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	cbuffer_read(user->u_black_in_buf, (unsigned char*)data, size);
+	return size;
+
+}
+
+void user_configure_tls(struct user *user, gnutls_session_t session)
+{
+	user->u_tls_session = session;
+	user->u_want_hand = 0;
+	user->u_tls_active = 0;
+	/* Configure the TLS session transport level. */
+	gnutls_transport_set_ptr(session, user);
+	gnutls_transport_set_push_function(session, gnutls_push_data);
+	gnutls_transport_set_pull_function(session, gnutls_pull_data);
+	gnutls_transport_set_lowat(session, 0);
+	/*
+	 * For security reasons, clear the red output as that can no
+	 * longer be trusted.
+	 */
+	cbuffer_clear(user->u_red_out_buf);
+}
+
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+	fd_set *wfds, struct timeval *deadline)
+{
+	/*
+	 * Execute all service handlers in case they alter what
+	 * files should be waited on.
+	 */
+	user_service_nofd(user);
+
+	/* Pretend that errored connections have immediate deadline. */
+	if (user->u_failure)
+		deadline->tv_sec = 0;
+	/* Adjust deadline. */
+	if (tv_compare(deadline, &user->u_deadline) > 0)
+		*deadline = user->u_deadline;
+
+	if (user->u_red_out_fd >= 0 && cbuffer_used(user->u_red_out_buf)) {
+		/* Red out is writable. */
+		FD_SET(user->u_red_out_fd, wfds);
+		if (*bound <= user->u_red_out_fd)
+			*bound = user->u_red_out_fd + 1;
+	}
+	if (user->u_black_fd >= 0 && !user->u_black_in_eof &&
+		/* Black in is readable. */
+		cbuffer_free(user->u_black_in_buf)) {
+		FD_SET(user->u_black_fd, rfds);
+		if (*bound <= user->u_black_fd)
+			*bound = user->u_black_fd + 1;
+	}
+	/* Intentionally bias red to black towards writing to black. */
+	if (user->u_black_fd >= 0 && !user->u_black_out_eof &&
+		/* Black out is writable. */
+		cbuffer_used(user->u_black_out_buf)) {
+		FD_SET(user->u_black_fd, wfds);
+		if (*bound <= user->u_black_fd)
+			*bound = user->u_black_fd + 1;
+	} else if (user->u_red_in_fd >= 0 &&
+		cbuffer_free(user->u_red_in_buf)) {
+		/* Red in is readable. */
+		FD_SET(user->u_red_in_fd, rfds);
+		if (*bound <= user->u_red_in_fd)
+			*bound = user->u_red_in_fd + 1;
+	}
+	if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) {
+		/* Red err is readable. */
+		FD_SET(user->u_red_err_fd, rfds);
+		if (*bound <= user->u_red_err_fd)
+			*bound = user->u_red_err_fd + 1;
+	}
+}
+
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds)
+{
+	int newr = 1;
+
+	/* Do this until no service handler makes any progress. */
+	while (newr) {
+		newr = 0;
+		newr = newr | black_in_handler(user, rfds);
+		newr = newr | black_out_handler(user, wfds);
+		newr = newr | red_in_handler(user, rfds);
+		newr = newr | red_out_handler(user, wfds);
+		newr = newr | red_err_handler(user, rfds);
+		newr = newr | black_out_eof_handler(user);
+		newr = newr | red_out_eof_handler(user);
+		newr = newr | connection_eof_handler(user);
+		newr = newr | black_to_red_handler(user);
+		newr = newr | red_to_black_handler(user);
+		newr = newr | rederr_to_black_handler(user);
+		newr = newr | timeout_handler(user);
+		newr = newr | handshake_handler(user);
+		newr = newr | tls_alert_handler(user);
+		newr = newr | process_delay_failure(user);
+	}
+}
+
+void user_service_nofd(struct user *user)
+{
+	fd_set rfds;
+	fd_set wfds;
+
+	/* Clear the fd sets, we don't do I/O here. */
+	FD_ZERO(&rfds);
+	FD_ZERO(&wfds);
+	user_service(user, &rfds, &wfds);
+}
+
+gnutls_session_t user_get_tls(struct user *user)
+{
+	if (user->u_tls_session && user->u_tls_active)
+		return user->u_tls_session;
+	else
+		return NULL;
+}
+
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err)
+{
+	user->u_red_in_fd = red_in;
+	user->u_red_out_fd = red_out;
+	user->u_red_err_fd = red_err;
+	if (red_in >= 0)
+		fcntl(red_in, F_SETFL, fcntl(red_in, F_GETFL) | O_NONBLOCK);
+	if (red_out >= 0)
+		fcntl(red_out, F_SETFL, fcntl(red_out, F_GETFL) | O_NONBLOCK);
+	if (red_err >= 0)
+		fcntl(red_err, F_SETFL, fcntl(red_err, F_GETFL) | O_NONBLOCK);
+}
+
+void user_clear_red_io(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+}
+
+struct cbuffer *user_get_red_in(struct user *user)
+{
+	if (user->u_red_in_fd >= 0)
+		return NULL;
+	else
+		return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		return NULL;
+	else
+		return user->u_red_out_buf;
+}
+
+int user_get_failure(struct user *user)
+{
+	return user->u_failure;
+}
+
+const char *user_get_error(struct user *user)
+{
+	return user->u_errmsg;
+}
+
+void user_send_red_in_eof(struct user *user)
+{
+	user->u_red_assume_more = 0;
+}
+
+struct cbuffer *user_get_red_err(struct user *user)
+{
+	if (user->u_red_err_fd >= 0 || user->u_red_in_have_data)
+		return NULL;
+	else
+		return user->u_red_err_buf;
+}
+
+void user_clear_deadline(struct user *user)
+{
+	user->u_deadline.tv_sec = -1;
+}
+
+size_t round_up(size_t base, size_t divide)
+{
+	return base + (divide - base % divide) % divide;
+}
+
+struct user *user_create(int black_fd, unsigned deadline_secs)
+{
+	struct timeval t;
+	size_t offset[5];
+#ifdef USE_TRAP_PAGING
+	size_t trap[6];
+#endif
+	int i;
+
+	for (i = 0; i < 5; i++)
+		offset[i] = i * BUFFERSIZE;
+
+	fcntl(black_fd, F_SETFL, fcntl(black_fd, F_GETFL) | O_NONBLOCK);
+
+	/* Compute the deadline field value. */
+	gettimeofday(&t, NULL);
+	t.tv_sec += deadline_secs;
+
+	/* How much to allocate for buffers? */
+	int r = 0;
+	size_t allocsize = 4 * BUFFERSIZE + ERR_BUFFERSIZE;
+
+	/* Allocate the primary structure. */
+	struct user *user = (struct user*)malloc(sizeof(struct user));
+	if (!user)
+		return NULL;
+
+	/* Allocate memory for buffer backing buffer. */
+#ifdef USE_TRAP_PAGING
+	allocsize = 4 * round_up(BUFFERSIZE, getpagesize()) +
+		round_up(ERR_BUFFERSIZE, getpagesize()) +
+		6 * getpagesize();
+
+	for (i = 0; i < 5; i++) {
+		offset[i] = i * round_up(BUFFERSIZE, getpagesize()) + (i + 1) * getpagesize();
+		trap[i] = offset[i] - getpagesize();
+	}
+	trap[5] = allocsize - getpagesize();
+
+	user->u_buf_backing_size = allocsize;
+	user->u_buf_backing = (unsigned char*)mmap(NULL,
+		user->u_buf_backing_size,
+		PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+	if (user->u_buf_backing == MAP_FAILED)
+		r = -1;
+	for (i = 0; i < 6; i++)
+		if (mprotect(user->u_buf_backing + trap[i], getpagesize(),
+			PROT_NONE) < 0) {
+			munmap(user->u_buf_backing, user->u_buf_backing_size);
+			r = -1;
+			break;
+		}
+#else
+	user->u_buf_backing = (unsigned char*)malloc(allocsize);
+	user->u_buf_backing_size = 0;
+	if (!user->u_buf_backing)
+		r = -1;
+#endif
+	if (r < 0) {
+		free(user);
+		return NULL;
+	}
+
+	user->u_black_in_eof = 0;
+	user->u_black_out_eof = 0;
+	user->u_black_in_d_eof = 0;
+	user->u_black_out_d_eof = 0;
+	user->u_want_read = 3;
+	user->u_want_write = 3;
+	user->u_want_hand = 3;
+	user->u_red_assume_more = 1;
+	user->u_red_in_have_data = 0;
+	user->u_red_err_hdr_sent = 0;
+	user->u_tls_active = 0;
+	user->u_black_fd = black_fd;
+	user->u_red_in_fd = -1;
+	user->u_red_out_fd = -1;
+	user->u_red_err_fd = -1;
+	user->u_black_in_buf = cbuffer_create(user->u_buf_backing +
+		offset[0], BUFFERSIZE);
+	user->u_black_out_buf = cbuffer_create(user->u_buf_backing +
+		offset[1], BUFFERSIZE);
+	user->u_red_in_buf = cbuffer_create(user->u_buf_backing +
+		offset[2], BUFFERSIZE);
+	user->u_red_out_buf = cbuffer_create(user->u_buf_backing +
+		offset[3], BUFFERSIZE);
+	user->u_red_err_buf = cbuffer_create(user->u_buf_backing +
+		offset[4], ERR_BUFFERSIZE);
+	if (!user->u_black_in_buf || !user->u_black_out_buf ||
+		!user->u_red_in_buf || !user->u_red_out_buf ||
+		!user->u_red_err_buf) {
+		/* Failed to allocate memory. */
+		cbuffer_destroy(user->u_black_in_buf);
+		cbuffer_destroy(user->u_black_out_buf);
+		cbuffer_destroy(user->u_red_in_buf);
+		cbuffer_destroy(user->u_red_out_buf);
+		cbuffer_destroy(user->u_red_err_buf);
+#ifdef USE_TRAP_PAGING
+		munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+		free(user->u_buf_backing);
+#endif
+		free(user);
+		return NULL;
+	}
+	user->u_deadline = t;
+	user->u_failure = USER_STILL_ACTIVE;
+	user->u_delay_failure = USER_STILL_ACTIVE;
+	user->u_errmsg = NULL;
+	user->u_tls_session = NULL;
+	user->u_delay_tls_failure = 0;
+	user->u_raw_tx = 0;
+	user->u_raw_rx = 0;
+	user->u_decoded_tx = 0;
+	user->u_decoded_rx = 0;
+	return user;
+}
+
+
+
+void user_release(struct user *user)
+{
+	if (!user->u_failure)
+		cleanup_user(user, USER_KILL, "User session killed");
+
+	cbuffer_destroy(user->u_black_in_buf);
+	cbuffer_destroy(user->u_black_out_buf);
+	cbuffer_destroy(user->u_red_in_buf);
+	cbuffer_destroy(user->u_red_out_buf);
+	cbuffer_destroy(user->u_red_err_buf);
+	free(user->u_errmsg);
+
+#ifdef USE_TRAP_PAGING
+	munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+	free(user->u_buf_backing);
+#endif
+	free(user);
+}
+
+int user_tls_configured(struct user *user)
+{
+	return (user->u_tls_session != NULL);
+}
+
+const char *user_explain_failure(int code)
+{
+	switch(code) {
+	case USER_STILL_ACTIVE:
+		return "Still active";
+	case USER_CONNECTION_END:
+		return "Connection closed";
+	case USER_LAYER4_ERROR:
+		return "Transport error";
+	case USER_TLS_ERROR:
+		return "TLS error";
+	case USER_TLS_HAND_ERROR:
+		return "TLS handshake error";
+	case USER_KILL:
+		return "User killed";
+	case USER_RED_FAILURE:
+		return "Subprocess failure";
+	case USER_TIMEOUT:
+		return "Timeout";
+	default:
+		return "Unknown error";
+	}
+}
+
+struct cbuffer *user_get_red_in_force(struct user *user)
+{
+	return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out_force(struct user *user)
+{
+	return user->u_red_out_buf;
+}
+
+struct cbuffer *user_get_red_err_force(struct user *user)
+{
+	return user->u_red_err_buf;
+}
+
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert)
+{
+	char error_buffer[8192];
+
+	if (user->u_delay_tls_failure)
+		return;
+
+	user->u_delay_tls_failure = 1;
+	user->u_delay_alert = alert;
+	sprintf(error_buffer, "TLS alert forced: %s",
+		gnutls_alert_get_name(alert));
+	user->u_errmsg = strdup(error_buffer);
+}
+
+int user_red_out_eofd(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		return 0;
+	if (user->u_tls_session && !user->u_black_in_d_eof)
+		return 0;
+	if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+		return 0;
+	if (!user->u_tls_session && !user->u_black_in_eof)
+		return 0;
+	return 1;
+}
+
+#define NAME_SPACE 60
+
+void print_name(char *buffer, const char *name)
+{
+	unsigned i;
+	if(strlen(name) >= NAME_SPACE) {
+		strncpy(buffer, name, NAME_SPACE - 4);
+		for(i = NAME_SPACE - 4; i < NAME_SPACE - 1; i++)
+			buffer[NAME_SPACE - i] = '.';
+	} else {
+		strcpy(buffer, name);
+		for(i = strlen(name); i < NAME_SPACE - 1; i++)
+			buffer[i] = ' ';
+	}
+	buffer[NAME_SPACE - 1] = ' ';
+}
+
+void print_boolean(FILE *filp, const char *name, unsigned value)
+{
+	char buffer[NAME_SPACE + 10];
+	print_name(buffer, name);
+	if (value)
+		strcpy(buffer + NAME_SPACE, "Set");
+	else
+		strcpy(buffer + NAME_SPACE, "Clear");
+	fprintf(filp, "%s\n", buffer);
+}
+
+void print_status(FILE *filp, const char *name, unsigned value)
+{
+	char buffer[NAME_SPACE + 40];
+	print_name(buffer, name);
+	if (value == 0)
+		strcpy(buffer + NAME_SPACE, "Success");
+	else if (value == 1)
+		strcpy(buffer + NAME_SPACE, "Insufficent data");
+	else if (value == 2)
+		strcpy(buffer + NAME_SPACE, "Insufficent space");
+	else if (value == 3)
+		strcpy(buffer + NAME_SPACE, "Not attempted");
+	else
+		sprintf(buffer + NAME_SPACE, "Unknown code %u", value);
+	fprintf(filp, "%s\n", buffer);
+}
+
+void print_fd(FILE *filp, const char *name, int fd)
+{
+	char buffer[NAME_SPACE + 40];
+	print_name(buffer, name);
+	if (fd >= 0)
+		sprintf(buffer + NAME_SPACE, "%i", fd);
+	else
+		strcpy(buffer + NAME_SPACE, "Not connected");
+	fprintf(filp, "%s\n", buffer);
+}
+
+void print_delayed_failure(FILE *filp, const char *name, unsigned dftype,
+	unsigned dfailure)
+{
+	char buffer[NAME_SPACE + 40];
+	print_name(buffer, name);
+	if (dftype == 0)
+		strcpy(buffer + NAME_SPACE, "<None>");
+	else if (dftype == 1)
+		sprintf(buffer + NAME_SPACE, "Code %u while handshaking", dfailure);
+	else if (dftype == 2)
+		sprintf(buffer + NAME_SPACE, "Code %u while transferring", dfailure);
+	else
+		sprintf(buffer + NAME_SPACE, "Code %u while in unknown %u", dfailure, dftype);
+	fprintf(filp, "%s\n", buffer);
+}
+
+void print_format(FILE *filp, const char *name, const char *format, ...)
+{
+	va_list args;
+	char buffer[NAME_SPACE + 120];
+	print_name(buffer, name);
+	va_start(args, format);
+	vsprintf(buffer + NAME_SPACE, format, args);
+	fprintf(filp, "%s\n", buffer);
+}
+
+void print_since(FILE *filp, const char *name, struct timeval event,
+	struct timeval now)
+{
+	char buffer[NAME_SPACE + 40];
+	uint64_t etime;
+	uint64_t ntime;
+	uint64_t tdelta;
+	print_name(buffer, name);
+	etime = (uint64_t)event.tv_sec * 1000000 + event.tv_usec;
+	ntime = (uint64_t)now.tv_sec * 1000000 + now.tv_usec;
+	if (etime < ntime)
+		tdelta = ntime - etime;
+	else
+		tdelta = 0;
+	if (tdelta < 1000000) {
+		sprintf(buffer + NAME_SPACE, "<1s ago");
+	} else {
+		sprintf(buffer + NAME_SPACE, "%llu.%03us ago",
+			(unsigned long long)(tdelta / 1000000),
+			(unsigned)(tdelta % 1000000 / 1000));
+	}
+	fprintf(filp, "%s\n", buffer);
+}
+
+void user_debug(struct user *user, FILE* filp)
+{
+	struct timeval tv;
+	if (!user) {
+		fprintf(filp, "\n\n\nNo transport present\n\n\n\n");
+		return;
+	}
+	gettimeofday(&tv, NULL);
+
+	fprintf(filp, "\n\n\nTransport level debugging output (version 3):\n");
+	print_boolean(filp, "Socket raw EOF received flag:", user->u_black_in_eof);
+	print_boolean(filp, "Socket raw EOF sent flag:", user->u_black_out_eof);
+	print_boolean(filp, "Encrypted EOF received flag:", user->u_black_in_d_eof);
+	print_boolean(filp, "Encrypted EOF sent flag:", user->u_black_out_d_eof);
+	print_boolean(filp, "Assume more local inptt flag:", user->u_red_assume_more);
+	print_boolean(filp, "TLS handshaked flag:", user->u_tls_active);
+	print_boolean(filp, "Received local input flag:", user->u_red_in_have_data);
+	print_status(filp, "Last read status:", user->u_want_read);
+	print_status(filp, "Last write status:", user->u_want_write);
+	print_status(filp, "Last handshake status:", user->u_want_hand);
+	print_format(filp, "Error header bytes sent:", "%u", user->u_red_err_hdr_sent);
+	print_delayed_failure(filp, "Delayed TLS failure:", user->u_delay_tls_failure,
+		user->u_delay_alert);
+	print_boolean(filp, "Seen input data flag:", user->u_seen_input_data);
+	print_fd(filp, "Socket fd:", user->u_black_fd);
+	print_fd(filp, "Local input fd:", user->u_red_in_fd);
+	print_fd(filp, "Local output fd:", user->u_red_out_fd);
+	print_fd(filp, "Local error fd:", user->u_red_err_fd);
+	if (user->u_deadline.tv_sec < 0)
+		print_format(filp, "Deadline:", "None set");
+	else
+		print_format(filp, "Deadline:", "%lu.%06lu\n",
+			(unsigned long)user->u_deadline.tv_sec,
+			(unsigned long)user->u_deadline.tv_usec);
+	print_format(filp, "Failure code:", "%i", user->u_failure);
+	if (user->u_errmsg)
+		print_format(filp, "Detailed error:", "%s", user->u_errmsg);
+	else
+		print_format(filp, "Detailed error:", "<Not set>");
+	print_format(filp, "Delayed failure code:", "%i", user->u_delay_failure);
+	if (user->u_tls_session)
+		print_format(filp, "TLS context:", "Present");
+	else
+		print_format(filp, "TLS context:", "Absent");
+	print_format(filp, "Socket input buffer:", "Used: %lu, free: %lu",
+		(unsigned long)cbuffer_used(user->u_black_in_buf),
+		(unsigned long)cbuffer_free(user->u_black_in_buf));
+	print_format(filp, "Socket output buffer:", "Used: %lu, free: %lu",
+		(unsigned long)cbuffer_used(user->u_black_out_buf),
+		(unsigned long)cbuffer_free(user->u_black_out_buf));
+	print_format(filp, "Local input buffer:", "Used: %lu, free: %lu",
+		(unsigned long)cbuffer_used(user->u_red_in_buf),
+		(unsigned long)cbuffer_free(user->u_red_in_buf));
+	print_format(filp, "Local output buffer:", "Used: %lu, free: %lu",
+		(unsigned long)cbuffer_used(user->u_red_out_buf),
+		(unsigned long)cbuffer_free(user->u_red_out_buf));
+	print_format(filp, "Local error buffer:", "Used: %lu, free: %lu",
+		(unsigned long)cbuffer_used(user->u_red_err_buf),
+		(unsigned long)cbuffer_free(user->u_red_err_buf));
+	print_format(filp, "TX bytes:", "Raw: %llu, Decoded: %llu",
+		user->u_raw_tx, user->u_decoded_tx);
+	print_format(filp, "RX bytes:", "Raw: %llu, Decoded: %llu",
+		user->u_raw_rx, user->u_decoded_rx);
+	print_since(filp, "Last TX:", user->u_last_tx, tv);
+	print_since(filp, "Last RX:", user->u_last_rx, tv);
+	fprintf(filp,"\n\n\n");
+}
diff --git a/git-over-tls/user.h b/git-over-tls/user.h
new file mode 100644
index 0000000..ac0d1fa
--- /dev/null
+++ b/git-over-tls/user.h
@@ -0,0 +1,367 @@
+/*
+ * 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 _user__h__included__
+#define _user__h__included__
+
+#include <gnutls/gnutls.h>
+#include "cbuffer.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminoloyy:
+ *	black:
+ *		Side of user session connected to socket. Transports
+ *		unencrypted or TLS data between peers.
+ *	red:
+ *		Side of user session connected to server loop or to
+ *		helper program.
+ *	red input:
+ *		Input buffer unencrypted data is read from. May be connected
+ *		to file descriptor. In that case that file descriptor is
+ *		read for data.
+ *	red output:
+ *		Output buffer decrypted data is written to. May be connected
+ *		to file descriptor. In that cse that file descriptor is
+ *		written with the data.
+ *	red error:
+ *		Input buffer error input is read from. May be connecte to
+ *		file descriptor, which is read for input data. If red input
+ *		file descriptor has data succesfully read, the red error buffer
+ *		is cleared and any further error input is redirected to bit
+ *		bucket. If red input closes with red error having data, then
+ *		that data is sent as ERR packet when red error has associated
+ *		file descriptor closed.
+ *	red_in:
+ *		File descriptor associated with red input.
+ *	red_out:
+ *		File descriptor associated with red output.
+ *	red_err:
+ *		File descriptor associated with red error.
+ *	deadline:
+ *		Time to disconnect (some) client on or perform some other
+ *		service.
+ */
+
+/* Main session structure. Opaque type. */
+struct user;
+
+/* Failure codes. */
+/* User still active. */
+#define USER_STILL_ACTIVE	0
+/* Connection normal end. */
+#define USER_CONNECTION_END	1
+/* Transport error. */
+#define USER_LAYER4_ERROR	-1
+/* TLS error while not handshaking. */
+#define USER_TLS_ERROR		-2
+/* TLS handshake error. */
+#define USER_TLS_HAND_ERROR	-3
+/* User killed. */
+#define USER_KILL		-4
+/* Red file descriptor I/O operation failure. */
+#define USER_RED_FAILURE	-5
+/* User timed out. */
+#define USER_TIMEOUT		-6
+
+/*
+ * Create new user session.
+ *
+ * Input:
+ *	black_fd	Black fd. Must be socket.
+ *	timeout_secs	Initial timeout in seconds.
+ *
+ * Output:
+ *	Return value	Newly created session, or NULL on out of
+ *			memory.
+ */
+struct user *user_create(int black_fd, unsigned timeout_secs);
+
+/*
+ * Configure session to use TLS. The TLS session must be preconfigured
+ * (credentials set, etc), but not handshaked.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	session		TLS session to assign.
+ */
+void user_configure_tls(struct user *user, gnutls_session_t session);
+
+/*
+ * Add current user session file descriptors that are ready to read or
+ * write to file descriptor sets for read or write. Also update dead-
+ * line if needed.
+ *
+ * Input:
+ *	user		The user session to handle.
+ *	bound		Current file descriptor bound. 0 if there are
+ *			no file descriptors in either set.
+ *	rfds		Read file descriptor set.
+ *	wfds		Write file descriptor set.
+ *	deadline	Current deadline for select.
+ *
+ * Output:
+ *	bound		Updated file descriptor bound.
+ *	rfds		Updated read file descriptor set.
+ *	wfds		Updated write file descriptor set.
+ *	deadline	Updated deadline for select.
+ */
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+	fd_set *wfds, struct timeval *deadline);
+
+/*
+ * Service this user.
+ *
+ * Input:
+ *	user		The user session to handle.
+ *	rfds		Ready to read file descriptors.
+ *	wfds		Ready to write file descriptors.
+ */
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds);
+
+/*
+ * Service this user without doing any I/O
+ *
+ * Input:
+ *	user		The user session to handle.
+ */
+void user_service_nofd(struct user *user);
+
+/*
+ * Get failure class.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	0 if connection is active, 1 if end
+ *			of connection, negative code if connection
+ *			ended with error. See USER_* defintions.
+ */
+int user_get_failure(struct user *user);
+
+/*
+ * Get explanation of failure code.
+ *
+ * Input:
+ *	code		The failure code.
+ *
+ * Output:
+ *	Return value	String explaining the code. Do not
+ *			free this.
+ */
+const char *user_explain_failure(int code);
+
+/*
+ * Get more detailed error message to explain the failure.
+ *
+ * Input:
+ *	user		The user session.
+ *
+ * Output:
+ *	Return value	Error message or NULL if there is
+ *			no more detailed error message. Do
+ *			not free this.
+ *
+ * Notes:
+ *	- Error message can be NULL or not independently of
+ *	  main failure status.
+ */
+const char *user_get_error(struct user *user);
+
+/*
+ * Has TLS been configured?
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Nonzero if configured, zero if not.
+ */
+/* Returns 1 if TLS has been configured, 0 otherwise. */
+int user_tls_configured(struct user *user);
+
+/*
+ * Return TLS session associated with user session.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If TLS is configured and has handshaked, the TLS
+ *			session. Otherwise NULL.
+ */
+gnutls_session_t user_get_tls(struct user *user);
+
+/*
+ * Free user structure. If user is still active, disconnect user hard.
+ *
+ * Input:
+ *	user		The user session to release.
+ */
+void user_release(struct user *user);
+
+/*
+ * Set red I/O file descriptors.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	red_in		Red input file descriptor, -1 for none.
+ *	red_out		Red output file descritpor, -1 for none.
+ *	red_err		Red error file descriptor, -1 for none.
+ *
+ * Notes:
+ *	- red_in and red_err must be read ends if present.
+ *	- red_out must be write end if present.
+ */
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err);
+
+/*
+ * Clear red I/O by closing red output and marking no red output file
+ * descriptor. This may be neeeded, since red out can't close without
+ * input to transfer.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ */
+void user_clear_red_io(struct user *user);
+
+/*
+ * Get red input buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_in is set to none, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_in(struct user *user);
+
+/*
+ * Get red output buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_out is set to none, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_out(struct user *user);
+
+/*
+ * Get red error buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_err is set to none and no data has been
+ *			received through red_in, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_err(struct user *user);
+
+/*
+ * Clear deadline (don't generate timeout anymore).
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ */
+void user_clear_deadline(struct user *user);
+
+/*
+ * Send EOF to red input.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *
+ * Notes:
+ *	- Only works if red input has no file descriptor associated.
+ *	- EOF is automatically sent if red_in encounters EOF.
+ */
+void user_send_red_in_eof(struct user *user);
+
+/*
+ * Force get red input buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red input buffer.
+ */
+struct cbuffer *user_get_red_in_force(struct user *user);
+
+/*
+ * Force get red output buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red output buffer.
+ */
+struct cbuffer *user_get_red_out_force(struct user *user);
+
+/*
+ * Force get red error buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red error buffer.
+ */
+struct cbuffer *user_get_red_err_force(struct user *user);
+
+/*
+ * Send fatal TLS alert.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	alert		The alert to send.
+ *
+ * Notes:
+ *	- Ignored if no TLS has been configured.
+ */
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert);
+
+/*
+ * Has red output been EOF'd?
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Nonzero if red out can receive no more data and
+ *			not connected to file descriptor, otherwise zero
+ *			(more data possible).
+ */
+int user_red_out_eofd(struct user *user);
+
+/*
+ * Print debugging output.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *	filp		Where to write the output.
+ */
+void user_debug(struct user *user, FILE *filp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
-- 
1.7.1.rc2.10.g714149

--
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]