{"thread":{"id":"22200","subject":"[RFC 0/2] Git-over-TLS (gits://) client side support","startedAt":"2010-01-13T13:19:44Z","lastAt":"2010-01-14T23:08:09Z","messageCount":28,"participants":["Ilari Liusvaara","Alex Riesen","Nguyen Thai Ngoc Duy","Andreas Krey","Avery Pennarun","Edward Z. Yang","Shawn O. Pearce"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"131467","messageId":"1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"22200","inReplyTo":null,"subject":"[RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T13:19:44Z","receivedAt":"2010-01-13T13:19:44Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"This is client-side support for Git-over-TLS (gits://). gits:// is\nversion of git:// protocol layered on top of TLS (Transport Layer\nSecurity). If using TLS, it is autenticated transport supporing\nfetching, pushing and remote archive (plus special commands that\nhave server-dependent meaning).\n\nNeeds GnuTLS, and adds new make option NO_GNUTLS that disables builing\nthis code.\n\nSupported underlying stream transports include TCP/IP, TCP/IPv6 and\nUnix domain sockets (including Linux abstract namespace).\n\nSupported authentication mechanisms include passwords, keypairs and on\nsome platforms Unix authentication if using unix domain sockets. Server\nis authenticated using keypair (hostkey).\n\nThe patch is split into two parts because it would be otherwise be\ntoo large for this list. Included are all the needed client side\nutilities (some of them run gpg internally).\n\nThe main repo for gits:// implementation is \ngit://repo.or.cz/git-daemon2.git , which includes selfstanding client\ncode and server code.\n\nIlari Liusvaara (2):\n  Git-over-TLS (gits://) client side support (part 1 of 2)\n  Git-over-TLS (gits://) client side support (part 2 of 2)\n\n Makefile                               |   23 +-\n git-over-tls/.gitignore                |    5 +\n git-over-tls/Makefile                  |   46 ++\n git-over-tls/cbuffer.c                 |  504 ++++++++++++\n git-over-tls/cbuffer.h                 |  304 +++++++\n git-over-tls/certificate.c             |  306 +++++++\n git-over-tls/certificate.h             |   28 +\n git-over-tls/connect.c                 |  263 ++++++\n git-over-tls/connect.h                 |   14 +\n git-over-tls/genkeypair.c              |   38 +\n git-over-tls/gensrpverifier.c          |  372 +++++++++\n git-over-tls/getkeyid.c                |  118 +++\n git-over-tls/gits-send-special-command |   22 +\n git-over-tls/home.c                    |   47 ++\n git-over-tls/home.h                    |   13 +\n git-over-tls/hostkey.c                 |  116 +++\n git-over-tls/hostkey.h                 |   15 +\n git-over-tls/hostkeymanager.c          |  305 +++++++\n git-over-tls/keypairs.c                |   60 ++\n git-over-tls/keypairs.h                |   16 +\n git-over-tls/main.c                    |  460 +++++++++++\n git-over-tls/misc.c                    |   15 +\n git-over-tls/misc.h                    |   27 +\n git-over-tls/mkcert.c                  |  507 ++++++++++++\n git-over-tls/prompt.c                  |  100 +++\n git-over-tls/prompt.h                  |   18 +\n git-over-tls/srp_askpass.c             |   90 ++\n git-over-tls/srp_askpass.h             |   14 +\n git-over-tls/user.c                    | 1384 ++++++++++++++++++++++++++++++++\n git-over-tls/user.h                    |  357 ++++++++\n 30 files changed, 5585 insertions(+), 2 deletions(-)\n create mode 100644 git-over-tls/.gitignore\n create mode 100644 git-over-tls/Makefile\n create mode 100644 git-over-tls/cbuffer.c\n create mode 100644 git-over-tls/cbuffer.h\n create mode 100644 git-over-tls/certificate.c\n create mode 100644 git-over-tls/certificate.h\n create mode 100644 git-over-tls/connect.c\n create mode 100644 git-over-tls/connect.h\n create mode 100644 git-over-tls/genkeypair.c\n create mode 100644 git-over-tls/gensrpverifier.c\n create mode 100644 git-over-tls/getkeyid.c\n create mode 100755 git-over-tls/gits-send-special-command\n create mode 100644 git-over-tls/home.c\n create mode 100644 git-over-tls/home.h\n create mode 100644 git-over-tls/hostkey.c\n create mode 100644 git-over-tls/hostkey.h\n create mode 100644 git-over-tls/hostkeymanager.c\n create mode 100644 git-over-tls/keypairs.c\n create mode 100644 git-over-tls/keypairs.h\n create mode 100644 git-over-tls/main.c\n create mode 100644 git-over-tls/misc.c\n create mode 100644 git-over-tls/misc.h\n create mode 100644 git-over-tls/mkcert.c\n create mode 100644 git-over-tls/prompt.c\n create mode 100644 git-over-tls/prompt.h\n create mode 100644 git-over-tls/srp_askpass.c\n create mode 100644 git-over-tls/srp_askpass.h\n create mode 100644 git-over-tls/user.c\n create mode 100644 git-over-tls/user.h\n"},{"id":"131468","messageId":"1263388786-6880-2-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"22200","inReplyTo":"1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2)","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T13:19:45Z","receivedAt":"2010-01-13T13:19:45Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n Makefile                               |   23 ++-\n git-over-tls/.gitignore                |    5 +\n git-over-tls/Makefile                  |   46 +++\n git-over-tls/cbuffer.c                 |  504 ++++++++++++++++++++++++++++++++\n git-over-tls/cbuffer.h                 |  304 +++++++++++++++++++\n git-over-tls/certificate.c             |  306 +++++++++++++++++++\n git-over-tls/certificate.h             |   28 ++\n git-over-tls/connect.c                 |  263 +++++++++++++++++\n git-over-tls/connect.h                 |   14 +\n git-over-tls/genkeypair.c              |   38 +++\n git-over-tls/gensrpverifier.c          |  372 +++++++++++++++++++++++\n git-over-tls/getkeyid.c                |  118 ++++++++\n git-over-tls/gits-send-special-command |   22 ++\n git-over-tls/home.c                    |   47 +++\n git-over-tls/home.h                    |   13 +\n git-over-tls/hostkey.c                 |  116 ++++++++\n git-over-tls/hostkey.h                 |   15 +\n git-over-tls/hostkeymanager.c          |  305 +++++++++++++++++++\n git-over-tls/keypairs.c                |   60 ++++\n git-over-tls/keypairs.h                |   16 +\n 20 files changed, 2613 insertions(+), 2 deletions(-)\n create mode 100644 git-over-tls/.gitignore\n create mode 100644 git-over-tls/Makefile\n create mode 100644 git-over-tls/cbuffer.c\n create mode 100644 git-over-tls/cbuffer.h\n create mode 100644 git-over-tls/certificate.c\n create mode 100644 git-over-tls/certificate.h\n create mode 100644 git-over-tls/connect.c\n create mode 100644 git-over-tls/connect.h\n create mode 100644 git-over-tls/genkeypair.c\n create mode 100644 git-over-tls/gensrpverifier.c\n create mode 100644 git-over-tls/getkeyid.c\n create mode 100755 git-over-tls/gits-send-special-command\n create mode 100644 git-over-tls/home.c\n create mode 100644 git-over-tls/home.h\n create mode 100644 git-over-tls/hostkey.c\n create mode 100644 git-over-tls/hostkey.h\n create mode 100644 git-over-tls/hostkeymanager.c\n create mode 100644 git-over-tls/keypairs.c\n create mode 100644 git-over-tls/keypairs.h\n\ndiff --git a/Makefile b/Makefile\nindex 4d2b99c..81cc848 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -163,6 +163,10 @@ all::\n # apostrophes to be ASCII so that cut&pasting examples to the shell\n # will work.\n #\n+# Define NO_GNUTLS if you don't want to use GnuTLS.\n+#\n+# Define NO_SRP if you don't want SRP support.\n+#\n # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's\n # MakeMaker (e.g. using ActiveState under Cygwin).\n #\n@@ -171,7 +175,6 @@ all::\n # Define NO_PYTHON if you do not want Python scripts or libraries at all.\n #\n # Define NO_TCLTK if you do not want Tcl/Tk GUI.\n-#\n # The TCL_PATH variable governs the location of the Tcl interpreter\n # used to optimize git-gui for your system.  Only used if NO_TCLTK\n # is not set.  Defaults to the bare 'tclsh'.\n@@ -291,7 +294,7 @@ TCL_PATH = tclsh\n TCLTK_PATH = wish\n PTHREAD_LIBS = -lpthread\n \n-export TCL_PATH TCLTK_PATH\n+export TCL_PATH TCLTK_PATH CC\n \n # sparse is architecture-neutral, which means that we need to tell it\n # explicitly what architecture to check for. Fix this up for yours..\n@@ -1248,6 +1251,9 @@ endif\n ifdef NO_IPV6\n \tBASIC_CFLAGS += -DNO_IPV6\n endif\n+ifdef NO_SRP\n+\tBASIC_CFLAGS += -DDISABLE_SRP\n+endif\n ifdef NO_UINTMAX_T\n \tBASIC_CFLAGS += -Duintmax_t=uint32_t\n endif\n@@ -1399,6 +1405,10 @@ TCLTK_PATH_SQ = $(subst ','\\'',$(TCLTK_PATH))\n \n LIBS = $(GITLIBS) $(EXTLIBS)\n \n+# In case some subdirectory wants to use git API libs.\n+GIT_PROGRAM_LINKLIBS = $(LIBS)\n+export GIT_PROGRAM_LINKLIBS\n+\n BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \\\n \t$(COMPAT_CFLAGS)\n LIB_OBJS += $(COMPAT_OBJS)\n@@ -1442,6 +1452,9 @@ endif\n ifndef NO_PERL\n \t$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all\n endif\n+ifndef NO_GNUTLS\n+\t$(QUIET_SUBDIR0)git-over-tls $(QUIET_SUBDIR1) prefix='$(prefix_SQ)' all\n+endif\n ifndef NO_PYTHON\n \t$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all\n endif\n@@ -1825,6 +1838,9 @@ install: all\n \t$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n \t$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'\n \t$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install\n+ifndef NO_GNUTLS\n+\t$(MAKE) -C git-over-tls DESTDIR_BIN='$(DESTDIR_SQ)$(bindir_SQ)' DESTDIR_GITEXEC='$(DESTDIR_SQ)$(gitexec_instdir_SQ)' install\n+endif\n ifndef NO_PERL\n \t$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install\n endif\n@@ -1956,6 +1972,9 @@ ifndef NO_PERL\n \t$(RM) gitweb/gitweb.cgi\n \t$(MAKE) -C perl clean\n endif\n+ifndef NO_GNUTLS\n+\t$(MAKE) -C git-over-tls clean\n+endif\n ifndef NO_PYTHON\n \t$(MAKE) -C git_remote_helpers clean\n endif\ndiff --git a/git-over-tls/.gitignore b/git-over-tls/.gitignore\nnew file mode 100644\nindex 0000000..546e49c\n--- /dev/null\n+++ b/git-over-tls/.gitignore\n@@ -0,0 +1,5 @@\n+/git-remote-gits\n+/gits-get-key-id\n+/gits-generate-keypair\n+/gits-generate-srp-verifier\n+/gits-hostkey\ndiff --git a/git-over-tls/Makefile b/git-over-tls/Makefile\nnew file mode 100644\nindex 0000000..7804ec7\n--- /dev/null\n+++ b/git-over-tls/Makefile\n@@ -0,0 +1,46 @@\n+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))\n+helper = git-remote-gits\n+programs = gits-get-key-id gits-hostkey gits-generate-keypair\n+scripts = gits-send-special-command\n+flags=\n+\n+# These can't be inherited from upper level or they won't work right...\n+ifndef V\n+\tQUIET_CC       = @echo '   ' CC $@;\n+\tQUIET_LINK     = @echo '   ' LINK $@;\n+endif\n+\n+ifdef NO_SRP\n+flags += -DDISABLE_SRP\n+else\n+programs += gits-generate-srp-verifier\n+endif\n+\n+all: $(programs) $(helper)\n+\n+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\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+gits-get-key-id: getkeyid.o certificate.o cbuffer.o home.o\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+ifndef NO_SRP\n+gits-generate-srp-verifier: gensrpverifier.o prompt.o\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+endif\n+\n+gits-hostkey: hostkeymanager.o home.o\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+gits-generate-keypair: genkeypair.o home.o mkcert.o cbuffer.o prompt.o\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+%.o: %.c\n+\t$(QUIET_CC)$(CC) $(CLFAGS) -c -o $@ $< $(flags) -I..\n+\n+install: all\n+\t$(INSTALL) $(programs) $(scripts) '$(DESTDIR_BIN)'\n+\t$(INSTALL) $(helper) '$(DESTDIR_GITEXEC)'\n+\n+clean:\n+\t$(RM) -f *.o $(programs) $(helper)\ndiff --git a/git-over-tls/cbuffer.c b/git-over-tls/cbuffer.c\nnew file mode 100644\nindex 0000000..e2adec7\n--- /dev/null\n+++ b/git-over-tls/cbuffer.c\n@@ -0,0 +1,504 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"cbuffer.h\"\n+#include <stdlib.h>\n+#include <assert.h>\n+#include <unistd.h>\n+#ifdef USE_UNIX_SCATTER_GATHER_IO\n+#include <sys/uio.h>\n+#endif\n+#include <errno.h>\n+#include <string.h>\n+\n+/*\n+ * NOTE: This code may not crash, call exit or anything similar unless\n+ * program state is corrupt or call parameters are completely invalid.\n+ * It also may not call Git APIs.\n+ */\n+\n+struct cbuffer\n+{\n+\t/* Base address for data area. */\n+\tunsigned char *cb_base;\n+\t/* Amount of free space. */\n+\tsize_t cb_free;\n+\t/* Amount of used space. */\n+\tsize_t cb_used;\n+\t/* Get pointer (relative to base) */\n+\tsize_t cb_get;\n+\t/* Put pointer (relative to base) */\n+\tsize_t cb_put;\n+\t/* Total size */\n+\tsize_t cb_size;\n+};\n+\n+/* Assert that invariants of circular buffer hold. */\n+static void assert_invariants(struct cbuffer *cbuf)\n+{\n+\tassert(cbuf->cb_free >= 0 && cbuf->cb_free <= cbuf->cb_size);\n+\tassert(cbuf->cb_used >= 0 && cbuf->cb_used <= cbuf->cb_size);\n+\tassert(cbuf->cb_free + cbuf->cb_used == cbuf->cb_size);\n+\tassert(cbuf->cb_get >= 0 && cbuf->cb_get <= cbuf->cb_size);\n+\tassert(cbuf->cb_put >= 0 && cbuf->cb_put <= cbuf->cb_size);\n+\tif (cbuf->cb_get == cbuf->cb_put)\n+\t\tassert(cbuf->cb_free == 0 || cbuf->cb_used == 0);\n+\telse if (cbuf->cb_get < cbuf->cb_put)\n+\t\tassert(cbuf->cb_get + cbuf->cb_used == cbuf->cb_put);\n+\telse\n+\t\tassert(cbuf->cb_put + cbuf->cb_free == cbuf->cb_get);\n+}\n+\n+/* Ack specified amount of data written. */\n+static void ack_write(struct cbuffer *cbuf, size_t wsize)\n+{\n+\tassert_invariants(cbuf);\n+\tassert(wsize <= cbuf->cb_free);\n+\t/*\n+\t * Writing data decreses free space, increases used space,\n+\t * increases put pointer, but it will wrap around if it\n+\t * goes past end of buffer.\n+\t */\n+\tcbuf->cb_free -= wsize;\n+\tcbuf->cb_used += wsize;\n+\tcbuf->cb_put += wsize;\n+\tif (cbuf->cb_put >= cbuf->cb_size)\n+\t\tcbuf->cb_put -= cbuf->cb_size;\n+\tassert_invariants(cbuf);\n+}\n+\n+/* Ack specified amount of data read (removing it). */\n+static void ack_read(struct cbuffer *cbuf, size_t rsize)\n+{\n+\tassert_invariants(cbuf);\n+\tassert(rsize <= cbuf->cb_used);\n+\t/*\n+\t * Reading data decreses used space, increases free space,\n+\t * increases get pointer, but it will wrap around if it\n+\t * goes past end of buffer.\n+\t */\n+\tcbuf->cb_free += rsize;\n+\tcbuf->cb_used -= rsize;\n+\tcbuf->cb_get += rsize;\n+\tif (cbuf->cb_get >= cbuf->cb_size)\n+\t\tcbuf->cb_get -= cbuf->cb_size;\n+\tassert_invariants(cbuf);\n+}\n+\n+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize)\n+{\n+\tstruct cbuffer *cbuf = (struct cbuffer*)malloc(sizeof(\n+\t\tstruct cbuffer));\n+\tif (cbuf) {\n+\t\tcbuf->cb_base = data;\n+\t\tcbuf->cb_size = cbuf->cb_free = datasize;\n+\t\tcbuf->cb_used = cbuf->cb_get = cbuf->cb_put = 0;\n+\t\tassert_invariants(cbuf);\n+\t}\n+\treturn cbuf;\n+}\n+\n+void cbuffer_destroy(struct cbuffer *cbuf)\n+{\n+\tif (cbuf) {\n+\t\tassert_invariants(cbuf);\n+\t\tfree(cbuf);\n+\t}\n+}\n+\n+size_t cbuffer_used(struct cbuffer *cbuf)\n+{\n+\tassert_invariants(cbuf);\n+\treturn cbuf->cb_used;\n+}\n+\n+size_t cbuffer_free(struct cbuffer *cbuf)\n+{\n+\tassert_invariants(cbuf);\n+\treturn cbuf->cb_free;\n+}\n+\n+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread)\n+{\n+\tassert_invariants(cbuf);\n+\n+\t/* Check that user doesn't try to read too much. */\n+\tif (toread > cbuf->cb_used)\n+\t\treturn -1;\n+\n+\tsize_t tocopy = toread;\n+\n+\t/*\n+\t * Limit the amount of data read to amount fits inside single\n+\t * segment. If get pointer is not greater or equal to put pointer,\n+\t * then entiere used space is only single segment.\n+\t */\n+\tif (cbuf->cb_get >= cbuf->cb_put)\n+\t\tif (tocopy > cbuf->cb_size - cbuf->cb_get)\n+\t\t\ttocopy = cbuf->cb_size - cbuf->cb_get;\n+\n+\tif (tocopy > 0) {\n+\t\t/* Copy the segment and mark it read. */\n+\t\tmemcpy(dest, cbuf->cb_base + cbuf->cb_get, tocopy);\n+\t\tack_read(cbuf, tocopy);\n+\t\t/* Adjust the request for subsequent segments. */\n+\t\ttoread -= tocopy;\n+\t\tdest += tocopy;\n+\t}\n+\n+\t/*\n+\t * If the read was incomplete, repeat the read request for the\n+\t * non-read tail.\n+\t */\n+\tif (toread > 0)\n+\t\treturn cbuffer_read(cbuf, dest, toread);\n+\n+\tassert_invariants(cbuf);\n+\n+\treturn 0;\n+}\n+\n+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread)\n+{\n+\tassert_invariants(cbuf);\n+\n+\t/* Check that user doesn't try to peek too much. */\n+\tif (toread > cbuf->cb_used)\n+\t\treturn -1;\n+\n+\t/* Is the used space as single segment or in two segments? */\n+\tif (cbuf->cb_get + toread > cbuf->cb_size) {\n+\t\t/* Two. We have to compute where segment boundary is and\n+\t\t   copy the data as two copies. */\n+\t\tsize_t firstseg = cbuf->cb_size - cbuf->cb_get;\n+\t\tmemcpy(dest, cbuf->cb_base + cbuf->cb_get, firstseg);\n+\t\tmemcpy(dest + firstseg, cbuf->cb_base, toread - firstseg);\n+\t} else {\n+\t\t/* One, data can be read as single copy. */\n+\t\tmemcpy(dest, cbuf->cb_base + cbuf->cb_get, toread);\n+\t}\n+\n+\tassert_invariants(cbuf);\n+\n+\treturn 0;\n+}\n+\n+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,\n+\tsize_t towrite)\n+{\n+\tassert_invariants(cbuf);\n+\n+\t/* Check that user doesn't try to write too much. */\n+\tif (towrite > cbuf->cb_free)\n+\t\treturn -1;\n+\n+\tsize_t tocopy = towrite;\n+\n+\t/*\n+\t * Limit the amount of data written to amount fits inside single\n+\t * segment. If put pointer is not greater or equal to get pointer,\n+\t * then entiere free space is only single segment.\n+\t */\n+\tif (cbuf->cb_put >= cbuf->cb_get)\n+\t\tif (tocopy > cbuf->cb_size - cbuf->cb_put)\n+\t\t\ttocopy = cbuf->cb_size - cbuf->cb_put;\n+\n+\tif (tocopy > 0) {\n+\t\t/* Copy the segment and mark it written. */\n+\t\tmemcpy(cbuf->cb_base + cbuf->cb_put, src, tocopy);\n+\t\tack_write(cbuf, tocopy);\n+\t\t/* Adjust the request for subsequent segments. */\n+\t\ttowrite -= tocopy;\n+\t\tsrc += tocopy;\n+\t}\n+\n+\t/*\n+\t * If the write was incomplete, repeat the write request for the\n+\t * non-written tail.\n+\t */\n+\tif (towrite > 0)\n+\t\treturn cbuffer_write(cbuf, src, towrite);\n+\n+\tassert_invariants(cbuf);\n+\n+\treturn 0;\n+}\n+\n+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove)\n+{\n+\tassert_invariants(dest);\n+\tassert_invariants(src);\n+\n+\t/* Check that amount to move isn't too great. */\n+\tif (tomove > dest->cb_free)\n+\t\treturn -1;\n+\tif (tomove > src->cb_used)\n+\t\treturn -1;\n+\n+\tsize_t tocopy = tomove;\n+\t/*\n+\t * Compute maximum number of bytes that is less than amount to\n+\t * move and amount of used/free space in current segments in\n+\t * both buffers.\n+\t */\n+\tif (dest->cb_put >= dest->cb_get)\n+\t\tif (tocopy > dest->cb_size - dest->cb_put)\n+\t\t\ttocopy = dest->cb_size - dest->cb_put;\n+\tif (src->cb_get >= src->cb_put)\n+\t\tif (tocopy > src->cb_size - src->cb_get)\n+\t\t\ttocopy = src->cb_size - src->cb_get;\n+\n+\tif (tocopy > 0) {\n+\t\t/* Move the segment. and mark it moved. */\n+\t\tmemcpy(dest->cb_base + dest->cb_put,\n+\t\t\tsrc->cb_base +src->cb_get, tocopy);\n+\t\tack_read(src, tocopy);\n+\t\tack_write(dest, tocopy);\n+\t\t/* Adjust request for subsequent segments. */\n+\t\ttomove -= tocopy;\n+\t}\n+\n+\t/* If request was incomplete, move the yet unmoved tail. */\n+\tif (tomove > 0)\n+\t\treturn cbuffer_move(dest, src, tomove);\n+\n+\tassert_invariants(dest);\n+\tassert_invariants(src);\n+\n+\treturn 0;\n+}\n+\n+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd)\n+{\n+\tssize_t r;\n+\n+\tassert_invariants(cbuf);\n+\n+\t/* Generate EAGAIN if needed (on no free space). */\n+\tif (cbuf->cb_free == 0) {\n+\t\terrno = EAGAIN;\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * If scatter-gather I/O is available, entiere buffer may be read at\n+\t * once. Otherwise only single segment can be read at time.\n+\t */\n+#ifdef USE_UNIX_SCATTER_GATHER_IO\n+\tstruct iovec areas[2];\n+\tint touse;\n+\n+\t/* One or two segments? */\n+\tif (cbuf->cb_put >= cbuf->cb_get) {\n+\t\t/* Two. */\n+\t\tareas[0].iov_base = cbuf->cb_base + cbuf->cb_put;\n+\t\tareas[0].iov_len = cbuf->cb_size - cbuf->cb_put;\n+\t\tareas[1].iov_base = cbuf->cb_base;\n+\t\tareas[1].iov_len = cbuf->cb_get;\n+\t\ttouse = 2;\n+\t} else {\n+\t\t/* One. */\n+\t\tareas[0].iov_base = cbuf->cb_base + cbuf->cb_put;\n+\t\tareas[0].iov_len = cbuf->cb_get - cbuf->cb_put;\n+\t\ttouse = 1;\n+\t}\n+\tr = readv(fd, areas, touse);\n+#else\n+\t/* Read into current segment. */\n+\tif (cbuf->cb_put >= cbuf->cb_get)\n+\t\tr = read(fd, cbuf->cb_base + cbuf->cb_put,\n+\t\t\tcbuf->cb_size - cbuf->cb_put);\n+\telse\n+\t\tr = read(fd, cbuf->cb_base + cbuf->cb_put,\n+\t\t\tcbuf->cb_get - cbuf->cb_put);\n+#endif\n+\t/* Ack any successfully read data as written. */\n+\tif (r > 0)\n+\t\tack_write(cbuf, (size_t)r);\n+\n+\tassert_invariants(cbuf);\n+\n+\treturn r;\n+}\n+\n+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd)\n+{\n+\tssize_t r;\n+\n+\tassert_invariants(cbuf);\n+\n+\t/* Generate EAGAIN if needed (on no used space). */\n+\tif (cbuf->cb_used == 0) {\n+\t\terrno = EAGAIN;\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * If scatter-gather I/O is available, entiere buffer may be written\n+\t * at once. Otherwise only single segment can be written at time.\n+\t */\n+#ifdef USE_UNIX_SCATTER_GATHER_IO\n+\tstruct iovec areas[2];\n+\tint touse;\n+\n+\t/* One or two segments? */\n+\tif (cbuf->cb_get >= cbuf->cb_put) {\n+\t\t/* Two. */\n+\t\tareas[0].iov_base = cbuf->cb_base + cbuf->cb_get;\n+\t\tareas[0].iov_len = cbuf->cb_size - cbuf->cb_get;\n+\t\tareas[1].iov_base = cbuf->cb_base;\n+\t\tareas[1].iov_len = cbuf->cb_put;\n+\t\ttouse = 2;\n+\t} else {\n+\t\t/* One. */\n+\t\tareas[0].iov_base = cbuf->cb_base + cbuf->cb_get;\n+\t\tareas[0].iov_len = cbuf->cb_put - cbuf->cb_get;\n+\t\ttouse = 1;\n+\t}\n+\tr = writev(fd, areas, touse);\n+#else\n+\t/* Write current segment. */\n+\tif (cbuf->cb_get >= cbuf->cb_put)\n+\t\tr = write(fd, cbuf->cb_base + cbuf->cb_get,\n+\t\t\tcbuf->cb_size - cbuf->cb_get);\n+\telse\n+\t\tr = write(fd, cbuf->cb_base + cbuf->cb_get,\n+\t\t\tcbuf->cb_put - cbuf->cb_get);\n+#endif\n+\t/* Ack any successfully written data as read. */\n+\tif (r > 0)\n+\t\tack_read(cbuf, (size_t)r);\n+\n+\tassert_invariants(cbuf);\n+\n+\treturn r;\n+}\n+\n+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,\n+\tsize_t *length)\n+{\n+\tassert_invariants(cbuf);\n+\n+\t/* Compute segment base. */\n+\t*base = cbuf->cb_base + cbuf->cb_get;\n+\n+\tif (!cbuf->cb_used) {\n+\t\t/* No used space -> empty segment. */\n+\t\t*length = 0;\n+\t} else if (cbuf->cb_get >= cbuf->cb_put) {\n+\t\t/* High segment is current. */\n+\t\t*length = cbuf->cb_size - cbuf->cb_get;\n+\t} else {\n+\t\t/* Low segment is current. */\n+\t\t*length = cbuf->cb_put - cbuf->cb_get;\n+\t}\n+\n+\tassert_invariants(cbuf);\n+}\n+\n+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length)\n+{\n+\tassert_invariants(cbuf);\n+\t/*\n+\t * This doesn't handle read being longer than single segment right,\n+\t * but that's undefined anyway.\n+\t */\n+\tack_read(cbuf, length);\n+\tassert_invariants(cbuf);\n+}\n+\n+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,\n+\tsize_t *length)\n+{\n+\tassert_invariants(cbuf);\n+\n+\t/* Compute segment base. */\n+\t*base = cbuf->cb_base + cbuf->cb_put;\n+\n+\tif (!cbuf->cb_free) {\n+\t\t/* No free space -> empty segment. */\n+\t\t*length = 0;\n+\t} else if (cbuf->cb_put >= cbuf->cb_get) {\n+\t\t/* High segment is current. */\n+\t\t*length = cbuf->cb_size - cbuf->cb_put;\n+\t} else {\n+\t\t/* Low segment is current. */\n+\t\t*length = cbuf->cb_get - cbuf->cb_put;\n+\t}\n+\n+\tassert_invariants(cbuf);\n+}\n+\n+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length)\n+{\n+\tassert_invariants(cbuf);\n+\t/*\n+\t * This doesn't handle write being longer than single segment right,\n+\t * but that's undefined anyway.\n+\t */\n+\tack_write(cbuf, length);\n+\tassert_invariants(cbuf);\n+}\n+\n+\n+void cbuffer_clear(struct cbuffer *cbuf)\n+{\n+\t/*\n+\t * Just resetting pointers and values to initial defaults clears\n+\t * all data.\n+\t */\n+\tcbuf->cb_used = cbuf->cb_put = cbuf->cb_get = 0;\n+\tcbuf->cb_free = cbuf->cb_size;\n+\tassert_invariants(cbuf);\n+}\n+\n+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,\n+\tsize_t limit)\n+{\n+\t/* Limit the request to maximum possible and do read request. */\n+\tif (limit > cbuffer_used(cbuf))\n+\t\tlimit = cbuffer_used(cbuf);\n+\tcbuffer_read(cbuf, dest, limit);\n+\treturn limit;\n+}\n+\n+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,\n+\tsize_t limit)\n+{\n+\t/* Limit the request to maximum possible and do write request. */\n+\tif (limit > cbuffer_free(cbuf))\n+\t\tlimit = cbuffer_free(cbuf);\n+\tcbuffer_write(cbuf, src, limit);\n+\treturn limit;\n+}\n+\n+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,\n+\tsize_t limit)\n+{\n+\t/* Limit the request to maximum possible and do move request. */\n+\tif (limit > cbuffer_free(dest))\n+\t\tlimit = cbuffer_free(dest);\n+\tif (limit > cbuffer_used(src))\n+\t\tlimit = cbuffer_used(src);\n+\tcbuffer_move(dest, src, limit);\n+\treturn limit;\n+}\n+\n+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src)\n+{\n+\tsize_t limit;\n+\t/*\n+\t * Limit the request to maximum possible and do move request.\n+\t * The move lacks limit so use free space in destination as\n+\t * first limit.\n+\t */\n+\tlimit = cbuffer_free(dest);\n+\tif (limit > cbuffer_used(src))\n+\t\tlimit = cbuffer_used(src);\n+\tcbuffer_move(dest, src, limit);\n+\treturn limit;\n+}\ndiff --git a/git-over-tls/cbuffer.h b/git-over-tls/cbuffer.h\nnew file mode 100644\nindex 0000000..4e07c5b\n--- /dev/null\n+++ b/git-over-tls/cbuffer.h\n@@ -0,0 +1,304 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _cbuffer__h__included__\n+#define _cbuffer__h__included__\n+\n+#include <stdlib.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+/*\n+ * Terminology:\n+ *\tSegment:\n+ *\t\tMemory-contigious range extending from get pointer, put\n+ *\t\tpointer or start of circular buffer till used/free type\n+ *\t\tchanges.\n+ *\tCurrent segment:\n+ *\t\tSegment starting from get pointer or put pointer.\n+ *\n+ */\n+\n+/* Primary circular buffer structure. Opaque type. */\n+struct cbuffer;\n+\n+/*\n+ * Create new circular buffer using specified data area. The data area\n+ * is not copied and must remain until circular buffer is destroyed.\n+ *\n+ * Inputs:\n+ *\tdata\t\tThe data area to back the circular buffer.\n+ *\tdatasize\tSize of backing data area.\n+ *\n+ * Outputs:\n+ *\treturn value\tThe newly created circular buffer, or NULL\n+ *\t\t\tif out of memory.\n+ */\n+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize);\n+\n+\n+/*\n+ * Free circular buffer. Data area is not freed.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to free.\n+ */\n+void cbuffer_destroy(struct cbuffer *cbuf);\n+\n+/*\n+ * Return number of bytes used in buffer (how many bytes can be read without\n+ * writes).\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to interrogate.\n+ *\n+ * Outputs:\n+ *\treturn value\tNumber of bytes data in buffer.\n+ */\n+size_t cbuffer_used(struct cbuffer *cbuf);\n+\n+/*\n+ * Return number of bytes free in buffer (how many bytes can be written\n+ * without reads).\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to interrogate.\n+ *\n+ * Outputs:\n+ *\treturn value\tNumber of bytes free in buffer.\n+ */\n+size_t cbuffer_free(struct cbuffer *cbuf);\n+\n+/*\n+ * Peek specified number of bytes from buffer. The bytes are not removed.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to peek.\n+ *\tdest\t\tDestination buffer to store the peeked data to.\n+ *\ttoread\t\tNumber of bytes to peek.\n+ *\n+ * Outputs:\n+ *\tReturn value\t0 on success, -1 if buffer has insufficient\n+ *\t\t\tamount of data.\n+ *\n+ */\n+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread);\n+\n+/*\n+ * Read specified number of bytes from buffer. The bytes read are\n+ * removed.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to read.\n+ *\tdest\t\tDestination buffer to store the read data to.\n+ *\ttoread\t\tNumber of bytes to read.\n+ *\n+ * Outputs:\n+ *\tReturn value\t0 on success, -1 if buffer has insufficient\n+ *\t\t\tamount of data.\n+ */\n+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread);\n+\n+/*\n+ * Write specified number of bytes to buffer.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tThe circular buffer to write.\n+ *\tsrc\t\tBuffer to read the written data from.\n+ *\ttowrite\t\tNumber of bytes to write.\n+ *\n+ * Outputs:\n+ *\tReturn value\t0 on success, -1 if buffer has insufficient\n+ *\t\t\tfree space.\n+ */\n+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,\n+\tsize_t towrite);\n+\n+/*\n+ * Move specified number of bytes from buffer to another. The data is\n+ * removed from source buffer.\n+ *\n+ * Inputs:\n+ *\tdest\t\tDestination circular buffer.\n+ *\tsrc\t\tSource cirrcular buffer.\n+ *\ttomove\t\tNumber of bytes to move.\n+ *\n+ * Outputs:\n+ *\tReturn value\t0 on success, -1 if insufficient source buffer\n+ *\t\t\tdata or insufficient destination buffer space.\n+ */\n+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove);\n+\n+/*\n+ * Call read on file descriptor. The call tries to read as much as\n+ * possible in one go (up to entiere free space of destination\n+ * buffer).\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to store the data to.\n+ *\tfd\t\tFile descriptor to read.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes read (>0) on success, 0 if\n+ *\t\t\tEOF on file descriptor or -1 if read failed.\n+ *\terrno\t\tSet by read() or readv() on failure.\n+ *\t\t\tEAGAIN if there is no free space in circular buffer.\n+ */\n+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd);\n+\n+/*\n+ * Call write on file descriptor. The call tries to write as much as\n+ * possible in one go (up to entiere used space of soruce\n+ * buffer).\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to read data from.\n+ *\tfd\t\tFile descriptor to write.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes written (>=0) on success,\n+ *\t\t\t-1 if write failed.\n+ *\terrno\t\tSet by write() or writev() on failure.\n+ *\t\t\tEAGAIN if there is no used space in circular buffer.\n+ */\n+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd);\n+\n+/*\n+ * Fill buffer read segment for direct from memory read operation on\n+ * circular buffer. Any read operation on buffer invalidates segment.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to read from.\n+ *\n+ * Outputs:\n+ *\tbase\t\tStart address of segemnt is written here.\n+ *\tlength\t\tLength of segment is written here.\n+ *\n+ * Notes:\n+ *\t- Returned length of 0 is only possible if there is no used\n+ *\t  space in buffer.\n+ *\t- The entiere used space may not be returned in single segment.\n+ */\n+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,\n+\tsize_t *length);\n+\n+/*\n+ * Commit read segment after direct read operation, marking data as\n+ * read. The read bytes are removed.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer read from.\n+ *\tlength\t\tLength of segment to mark as read. Must\n+ *\t\t\tbe at most the length gotten from\n+ *\t\t\tcbuffer_fill_r_segment.\n+ */\n+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length);\n+\n+/*\n+ * Fill buffer write segment for direct to memory write operation on\n+ * circular buffer. Any write operation on buffer invalidates segment.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to write to.\n+ *\n+ * Outputs:\n+ *\tbase\t\tStart address of segment is written here.\n+ *\tlength\t\tLength of segment is written here.\n+ *\n+ * Notes:\n+ *\t- Returned length of 0 is only possible if there is no free\n+ *\t  space in buffer.\n+ *\t- The entiere free space may not be returned in single segment.\n+ */\n+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,\n+\tsize_t *length);\n+\n+/*\n+ * Commit write segment after direct write operation, marking data as\n+ * written\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer written to.\n+ *\tlength\t\tLength of segment to mark as written. Must\n+ *\t\t\tbe at most the length gotten from\n+ *\t\t\tcbuffer_fill_w_segment.\n+ */\n+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length);\n+\n+/*\n+ * Clear all data in cbuffer, freeing any used space.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCirecular buffer to clear.\n+ */\n+void cbuffer_clear(struct cbuffer *cbuf);\n+\n+/*\n+ * Read number of bytes from circular buffer smaller than limit.\n+ * The read bytes are removed.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to read.\n+ *\tdest\t\tDestination to store the read data.\n+ *\tlimit\t\tMaximum number of bytes to read.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes read.\n+ */\n+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,\n+\tsize_t limit);\n+\n+/*\n+ * Write number of bytes to circular buffer smaller than limit.\n+ *\n+ * Inputs:\n+ *\tcbuf\t\tCircular buffer to write.\n+ *\tdest\t\tSource to read the wrritten data.\n+ *\tlimit\t\tMaximum number of bytes to write.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes written.\n+ */\n+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,\n+\tsize_t limit);\n+\n+/*\n+ * Move number of bytes from circular buffer to another smaller than limit.\n+ * The read bytes are removed from source circular buffer.\n+ *\n+ * Inputs:\n+ *\tdest\t\tDestination circular buffer.\n+ *\tsrc\t\tSource circular buffer.\n+ *\tlimit\t\tMaximum number of bytes to move.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes moved.\n+ */\n+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,\n+\tsize_t limit);\n+\n+/*\n+ * Move as much bytes from circular buffer to another as possible.\n+ * The read bytes are removed from source circular buffer.\n+ *\n+ * Inputs:\n+ *\tdest\t\tDestination circular buffer.\n+ *\tsrc\t\tSource circular buffer.\n+ *\n+ * Outputs:\n+ *\tReturn value\tNumber of bytes moved.\n+ */\n+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src);\n+\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\ndiff --git a/git-over-tls/certificate.c b/git-over-tls/certificate.c\nnew file mode 100644\nindex 0000000..adeb776\n--- /dev/null\n+++ b/git-over-tls/certificate.c\n@@ -0,0 +1,306 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"certificate.h\"\n+#include \"cbuffer.h\"\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <signal.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#include \"run-command.h\"\n+#endif\n+\n+#define CERT_MAX 65536\n+\n+static long read_short(struct cbuffer *buffer)\n+{\n+\tunsigned char x[2];\n+\tif (cbuffer_read(buffer, x, 2) < 0)\n+\t\treturn -1;\n+\n+\treturn ((long)x[0] << 8) | ((long)x[1]);\n+}\n+\n+\n+static int unseal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,\n+\tconst char *unsealer)\n+{\n+\tstruct child_process child;\n+\tchar **argv;\n+\tchar *unsealer_copy;\n+\tint splits = 0;\n+\tint escape = 0;\n+\tint ridx, widx, tidx;\n+\tconst char *i;\n+\n+\tsignal(SIGPIPE, SIG_IGN);\n+\n+\tfor (i = unsealer; *i; i++) {\n+\t\tif (escape)\n+\t\t\tescape = 0;\n+\t\telse if (*i == '\\\\')\n+\t\t\tescape = 1;\n+\t\telse if (*i == ' ')\n+\t\t\tsplits++;\n+\t}\n+\n+\targv = xmalloc((splits + 2) * sizeof(char*));\n+\targv[splits + 1] = NULL;\n+\n+\tunsealer_copy = xstrdup(unsealer);\n+\targv[0] = unsealer_copy;\n+\n+\tridx = 0;\n+\twidx = 0;\n+\ttidx = 1;\n+\tescape = 0;\n+\twhile (unsealer_copy[ridx]) {\n+\t\tif (escape) {\n+\t\t\tescape = 0;\n+\t\t\tunsealer_copy[widx++] = unsealer_copy[ridx++];\n+\t\t} else if (unsealer_copy[ridx] == '\\\\') {\n+\t\t\tridx++;\n+\t\t\tescape = 1;\n+\t\t} else if (unsealer_copy[ridx] == ' ') {\n+\t\t\tunsealer_copy[widx++] = '\\0';\n+\t\t\targv[tidx++] = unsealer_copy + widx;\n+\t\t\tridx++;\n+\t\t} else\n+\t\t\tunsealer_copy[widx++] = unsealer_copy[ridx++];\n+\t}\n+\tunsealer_copy[widx] = '\\0';\n+\n+\tmemset(&child, 0, sizeof(child));\n+\tchild.argv = (const char**)argv;\n+\tchild.in = -1;\n+\tchild.out = -1;\n+\tchild.err = 0;\n+\tif (start_command(&child))\n+\t\tdie(\"Running keypair unsealer command failed\");\n+\n+\twhile (1) {\n+\t\tint bound;\n+\t\tfd_set rf;\n+\t\tfd_set wf;\n+\t\tint r;\n+\n+\t\tFD_ZERO(&rf);\n+\t\tFD_ZERO(&wf);\n+\t\tFD_SET(child.out, &rf);\n+\t\tif (cbuffer_used(sealed))\n+\t\t\tFD_SET(child.in, &wf);\n+\t\telse\n+\t\t\tclose(child.in);\n+\n+\t\tif (cbuffer_used(sealed))\n+\t\t\tbound = ((child.out > child.in) ? child.out :\n+\t\t\t\tchild.in) + 1;\n+\t\telse\n+\t\t\tbound = child.out + 1;\n+\n+\t\tr = select(bound, &rf, &wf, NULL, NULL);\n+\t\tif (r < 0 && r != EINTR)\n+\t\t\tdie_errno(\"Select failed\");\n+\t\tif (r < 0) {\n+\t\t\tFD_ZERO(&rf);\n+\t\t\tFD_ZERO(&wf);\n+\t\t\tperror(\"select\");\n+\t\t}\n+\n+\t\tif (FD_ISSET(child.out, &rf)) {\n+\t\t\tr = cbuffer_read_fd(unsealed, child.out);\n+\t\t\tif (r < 0 && errno != EINTR && errno != EAGAIN)\n+\t\t\t\tdie_errno(\"Read from unsealer failed\");\n+\t\t\tif (r < 0 && errno == EAGAIN)\n+\t\t\t\tif (!cbuffer_free(unsealed))\n+\t\t\t\t\tdie(\"Keypair too big\");\n+\t\t\tif (r < 0)\n+\t\t\t\tperror(\"read\");\n+\t\t\tif (r == 0)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (FD_ISSET(child.in, &wf)) {\n+\t\t\tr = cbuffer_write_fd(sealed, child.in);\n+\t\t\tif (r < 0 && errno == EPIPE)\n+\t\t\t\tdie(\"Unsealer exited unexpectedly\");\n+\t\t\tif (r < 0 && errno != EINTR && errno != EAGAIN)\n+\t\t\t\tdie_errno(\"Write to unsealer failed\");\n+\t\t\tif (r < 0)\n+\t\t\t\tperror(\"write\");\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child))\n+\t\tdie(\"Keypair unsealer command failed\");\n+\n+\treturn 0;\n+}\n+\n+\n+struct certificate parse_certificate(const char *name, int *errorcode)\n+{\n+\tstruct cbuffer *sealed = NULL;\n+\tstruct cbuffer *unsealed = NULL;\n+\tunsigned char sealed_buf[CERT_MAX];\n+\tunsigned char unsealed_buf[CERT_MAX];\n+\tstruct certificate cert;\n+\tint fd;\n+\tunsigned char head[10];\n+\tlong tmp;\n+\n+\t*errorcode = CERTERR_OK;\n+\tcert.public_key.data = NULL;\n+\tcert.public_key.size = 0;\n+\tcert.private_key.data = NULL;\n+\tcert.private_key.size = 0;\n+\n+\tsealed = cbuffer_create(sealed_buf, CERT_MAX);\n+\tif (!sealed)\n+\t\tdie(\"Ran out of memory\");\n+\n+\tunsealed = cbuffer_create(unsealed_buf, CERT_MAX);\n+\tif (!unsealed)\n+\t\tdie(\"Ran out of memory\");\n+\n+\tfd = open(name, O_RDONLY);\n+\tif (fd < 0) {\n+\t\tif (errno == ENOENT)\n+\t\t\t*errorcode = CERTERR_NOCERT;\n+\t\telse\n+\t\t\t*errorcode = CERTERR_CANTREAD;\n+\t\tgoto out_unsealed;\n+\t}\n+\n+\twhile (1) {\n+\t\tssize_t r = cbuffer_read_fd(sealed, fd);\n+\t\tif (r == 0)\n+\t\t\tbreak;\n+\t\tif (r < 0 && errno == EAGAIN) {\n+\t\t\tif (!cbuffer_free(sealed)) {\n+\t\t\t\t*errorcode = CERTERR_TOOBIG;\n+\t\t\t\tgoto out_close;\n+\t\t\t}\n+\t\t} else if (r < 0 && errno != EINTR) {\n+\t\t\t*errorcode = CERTERR_CANTREAD;\n+\t\t\tgoto out_close;\n+\t\t}\n+\t}\n+\n+\thead[9] = 0;\n+\tif (cbuffer_read(sealed, head, 9) < 0) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_close;\n+\t}\n+\tif (strcmp((char*)head, \"GITSSCERT\") &&\n+\t\tstrcmp((char*)head, \"GITSUCERT\")) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_close;\n+\t}\n+\n+\tif (!strcmp((char*)head, \"GITSSCERT\")) {\n+\t\t/* Sealed certificate. */\n+\t\tchar *unsealer;\n+\t\tint s;\n+\t\ttmp = read_short(sealed);\n+\t\tif (tmp <= 0) {\n+\t\t\t*errorcode = CERTERR_INVALID;\n+\t\t\tgoto out_close;\n+\t\t}\n+\t\tunsealer = xmalloc(tmp + 1);\n+\t\tunsealer[tmp] = '\\0';\n+\t\tif (cbuffer_read(sealed, (unsigned char*)unsealer, tmp) < 0) {\n+\t\t\tfree(unsealer);\n+\t\t\t*errorcode = CERTERR_INVALID;\n+\t\t\tgoto out_close;\n+\t\t}\n+\t\ts = unseal_cert(sealed, unsealed, unsealer);\n+\t\tfree(unsealer);\n+\t\tif (s < 0) {\n+\t\t\t*errorcode = s;\n+\t\t\tgoto out_close;\n+\t\t}\n+\t} else {\n+\t\t/* Unsealed certificate. */\n+\t\tcbuffer_move_nolimit(unsealed, sealed);\n+\t}\n+\n+\tcert.private_key.data = NULL;\n+\tcert.public_key.data = NULL;\n+\n+\ttmp = read_short(unsealed);\n+\tif (tmp < 0) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_close;\n+\t}\n+\tcert.private_key.size = tmp;\n+\tcert.private_key.data = (unsigned char*)gnutls_malloc(tmp);\n+\tif (!cert.private_key.data)\n+\t\tdie(\"Ran out of memory\");\n+\n+\tif (cbuffer_read(unsealed, cert.private_key.data,\n+\t\tcert.private_key.size) < 0) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_private;\n+\t}\n+\n+\ttmp = read_short(unsealed);\n+\tif (tmp < 0) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_close;\n+\t}\n+\tcert.public_key.size = tmp;\n+\tcert.public_key.data = (unsigned char*)gnutls_malloc(tmp);\n+\tif (!cert.public_key.data)\n+\t\tdie(\"Ran out of memory\");\n+\n+\tif (cbuffer_read(unsealed, cert.public_key.data,\n+\t\tcert.public_key.size) < 0) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_public;\n+\t}\n+\n+\tif (cbuffer_used(unsealed)) {\n+\t\t*errorcode = CERTERR_INVALID;\n+\t\tgoto out_public;\n+\t}\n+\n+\tgoto out_close;\n+\n+out_public:\n+\tgnutls_free(cert.private_key.data);\n+out_private:\n+\tgnutls_free(cert.private_key.data);\n+out_close:\n+\tclose(fd);\n+out_unsealed:\n+\tcbuffer_destroy(unsealed);\n+\tcbuffer_destroy(sealed);\n+\treturn cert;\n+}\n+\n+const char *cert_parse_strerr(int errcode)\n+{\n+\tswitch(errcode) {\n+\tcase CERTERR_OK:\n+\t\treturn \"Success\";\n+\tcase CERTERR_NOCERT:\n+\t\treturn \"No such keypair\";\n+\tcase CERTERR_INVALID:\n+\t\treturn \"Keypair file corrupt\";\n+\tcase CERTERR_CANTREAD:\n+\t\treturn \"Can't read keypair file\";\n+\tcase CERTERR_TOOBIG:\n+\t\treturn \"Keypair too big\";\n+\t}\n+\treturn \"<Unknown error>\";\n+}\ndiff --git a/git-over-tls/certificate.h b/git-over-tls/certificate.h\nnew file mode 100644\nindex 0000000..5ee355a\n--- /dev/null\n+++ b/git-over-tls/certificate.h\n@@ -0,0 +1,28 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _certificate__h__included__\n+#define _certificate__h__included__\n+\n+#include <gnutls/gnutls.h>\n+\n+#define CERTERR_OK\t\t0\n+#define CERTERR_NOCERT\t\t-2\n+#define CERTERR_INVALID\t\t-3\n+#define CERTERR_CANTREAD\t-4\n+#define CERTERR_TOOBIG\t\t-5\n+\n+struct certificate\n+{\n+\tgnutls_datum_t public_key;\n+\tgnutls_datum_t private_key;\n+};\n+\n+struct certificate parse_certificate(const char *name, int *errorcode);\n+const char *cert_parse_strerr(int errcode);\n+\n+#endif\ndiff --git a/git-over-tls/connect.c b/git-over-tls/connect.c\nnew file mode 100644\nindex 0000000..8f19bc8\n--- /dev/null\n+++ b/git-over-tls/connect.c\n@@ -0,0 +1,263 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"connect.h\"\n+#include <netdb.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <sys/types.h>\n+#include <sys/socket.h>\n+#ifndef WIN32\n+#include <sys/un.h>\n+#endif\n+#include <netinet/in.h>\n+#include <netinet/ip.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+#ifndef UNIX_PATH_MAX\n+#define UNIX_PATH_MAX 108\n+#endif\n+\n+static int connect_unix(const char* path)\n+{\n+#ifndef WIN32\n+\tstruct sockaddr_un saddru;\n+\tint fd, ret, plen;\n+\n+\tif (strlen(path) > UNIX_PATH_MAX - 1)\n+\t\tdie(\"Unix socket path too long\");\n+\n+\tsaddru.sun_family = AF_UNIX;\n+\tstrcpy(saddru.sun_path, path);\n+\tif (*saddru.sun_path == '@')\n+\t\t*saddru.sun_path = '\\0';\n+\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd < 0)\n+\t\tdie_errno(\"Can't create socket\");\n+\tif (*path == '@')\n+\t\tplen = (int)((char*)saddru.sun_path - (char*)&saddru) +\n+\t\t\tstrlen(path);\n+\telse\n+\t\tplen = (int)sizeof(saddru);\n+\tret = connect(fd, (struct sockaddr*)&saddru, plen);\n+\tif (ret < 0) {\n+\t\tdie_errno(\"Can't connect to %s\", path);\n+\t}\n+\treturn fd;\n+#else\n+\tdie(\"Unix domain sockets not supported by this build\");\n+#endif\n+}\n+\n+static int connect_address(const char* host, unsigned short port,\n+\tint protocol, struct sockaddr* addr, int size)\n+{\n+\tchar address[1024];\n+\tint fd, ret;\n+\tconst unsigned char* _addr;\n+\n+\tfd = socket(addr->sa_family, SOCK_STREAM, protocol);\n+\tif (fd < 0) {\n+\t\terror(\"Can't create socket: %s\", strerror(errno));\n+\t\treturn -1;\n+\t}\n+\tswitch(addr->sa_family) {\n+\tcase AF_INET:\n+\t\t_addr = (const unsigned char*)\n+\t\t\t&(((struct sockaddr_in*)addr)->sin_addr.s_addr);\n+\t\tsprintf(address, \"%u.%u.%u.%u\", _addr[0], _addr[1],\n+\t\t\t_addr[2], _addr[3]);\n+\t\tbreak;\n+#ifndef NO_IPV6\n+\tcase AF_INET6:\n+\t\t_addr = (const unsigned char*)\n+\t\t\t((struct sockaddr_in6*)addr)->sin6_addr.s6_addr;\n+\t\tsprintf(address, \"%02X%02X:%02X%02X:%02X%02X:%02X%02X:\"\n+\t\t\t\"%02X%02X:%02X%02X:%02X%02X:%02X%02X\",\n+\t\t\t_addr[0], _addr[1], _addr[2], _addr[3],\n+\t\t\t_addr[4], _addr[5], _addr[6], _addr[7],\n+\t\t\t_addr[8], _addr[9], _addr[10], _addr[11],\n+\t\t\t_addr[12], _addr[13], _addr[14], _addr[15]);\n+\t\tbreak;\n+#endif\n+\tdefault:\n+\t\tsprintf(address, \"<unknown address type>\");\n+\t\tbreak;\n+\t}\n+\tret = connect(fd, addr, size);\n+\tif (ret < 0) {\n+\t\terror(\"Can't connect to host %s[%s] port %u: %s\",\n+\t\t\thost, address, port, strerror(errno));\n+\t\treturn -1;\n+\t}\n+\treturn fd;\n+}\n+\n+int connect_gethostbyname(const char* _host,\n+\tunsigned short _port, uint32_t scope)\n+{\n+#ifndef NO_IPV6\n+\tstruct addrinfo hints;\n+\tstruct addrinfo* returned;\n+\tchar port[10];\n+\tint fd, ret;\n+\n+\tmemset(&hints, 0, sizeof(hints));\n+\thints.ai_socktype = SOCK_STREAM;\n+\n+\tsprintf(port, \"%u\", _port);\n+\tret = getaddrinfo(_host, port, &hints, &returned);\n+\tif (ret)\n+\t\tdie(\"getaddrinfo(%s, %s, ...): %s\", _host, port,\n+\t\t\tgai_strerror(ret));\n+\n+\twhile (returned) {\n+\t\tif (returned->ai_family == AF_INET6)\n+\t\t\t((struct sockaddr_in6*)returned->ai_addr)->\n+\t\t\t\tsin6_scope_id = scope;\n+\t\telse if (scope)\n+\t\t\twarning(\"Scope is ignored for non-IPv6 addresses\");\n+\n+\t\tfd = connect_address(_host, _port, returned->ai_protocol,\n+\t\t\treturned->ai_addr, returned->ai_addrlen);\n+\n+\t\tif (fd >= 0)\n+\t\t\tgoto out;\n+\t\treturned = returned->ai_next;\n+\t}\n+\n+\tdie(\"Can't connect to host %s\", _host);\n+\n+out:\n+\tfreeaddrinfo(returned);\n+\treturn fd;\n+#else\n+\tstruct hostent *host;\n+\tint fd;\n+\tstatic struct sockaddr_in saddr4;\n+\n+\thost = gethostbyname(_host);\n+\tif (!host || !host->h_addr)\n+\t\tdie(\"Can't find host %s\", _host);\n+\n+next_address:\n+\tif (host->h_addrtype == AF_INET) {\n+\t\tmemset(&saddr4, 0, sizeof(saddr4));\n+\t\tsaddr4.sin_family = AF_INET;\n+\t\tmemcpy(&saddr4.sin_addr, host->h_addr_list[0], 4);\n+\t\tsaddr4.sin_port = htons(_port);\n+\t\tfd = connect_address(_host, _port, 0,\n+\t\t\t(struct sockaddr*)&saddr4,\n+\t\t\tsizeof(struct sockaddr_in));\n+\t\tif (scope)\n+\t\t\twarning(\"Scope is ignored for non-IPv6 addresses\");\n+\t} else\n+\t\tdie(\"Host %s has unknown address type\", _host);\n+\n+\tif (fd >= 0)\n+\t\tgoto out;\n+\n+\thost->h_addr_list++;\n+\tif (host->h_addr_list)\n+\t\tgoto next_address;\n+\n+\tif (scope > 0)\n+\t\tdie(\"Can't connect to host %s%%u\", _host, scope);\n+\telse\n+\t\tdie(\"Can't connect to host %s\", _host);\n+out:\n+\treturn fd;\n+#endif\n+}\n+\n+/* Parse character as base-10 digit. */\n+static int char_to_int(char ch)\n+{\n+\tswitch(ch) {\n+\tcase '0':\n+\t\treturn 0;\n+\tcase '1':\n+\t\treturn 1;\n+\tcase '2':\n+\t\treturn 2;\n+\tcase '3':\n+\t\treturn 3;\n+\tcase '4':\n+\t\treturn 4;\n+\tcase '5':\n+\t\treturn 5;\n+\tcase '6':\n+\t\treturn 6;\n+\tcase '7':\n+\t\treturn 7;\n+\tcase '8':\n+\t\treturn 8;\n+\tcase '9':\n+\t\treturn 9;\n+\tdefault:\n+\t\treturn -1;\n+\t}\n+}\n+\n+static uint32_t touint32_t(const char* num)\n+{\n+\tuint32_t x = 0;\n+\tunsigned i;\n+\n+\t/* Blank string is not valid number. */\n+\tif (!*num)\n+\t\tdie(\"Invalid scope id '%s'\", num);\n+\n+\t/* 0 is special case, makes it easier to deal with zeros. */\n+\tif (!strcmp(num, \"0\"))\n+\t\treturn 0;\n+\n+\t/*\n+\t * Valid uints numbers are 0-2^32-1, but because we handled 0 as\n+\t * special case, the valid range can be 1-2^32-1 here.\n+\t */\n+\tfor (i = 0; num[i]; i++) {\n+\t\tchar ch = char_to_int(num[i]);\n+\t\tif (ch < 0)\n+\t\t\tdie(\"Invalid scope id '%s'\", num);\n+\t\tif (ch == 0 && x == 0)\n+\t\t\tdie(\"Invalid scope id '%s'\", num);\n+\t\tif (x > 429496729 || (x > 429496728 && ch > 5))\n+\t\t\tdie(\"Invalid scope id '%s'\", num);\n+\t\tx = x * 10 + ch;\n+\t}\n+\treturn x;\n+}\n+\n+int connect_host(const char* _host, unsigned short _port)\n+{\n+\tuint32_t scope = 0;\n+\tchar* scopestart;\n+\tchar* hostcopy;\n+\n+\tif (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/'))\n+\t\treturn connect_unix(_host);\n+\n+\thostcopy = xmalloc(strlen(_host) + 1);\n+\tstrcpy(hostcopy, _host);\n+\tscopestart = strchr(hostcopy, '%');\n+\tif (scopestart) {\n+\t\t*(scopestart++) = '\\0';\n+\t\tscope = touint32_t(scopestart);\n+\t}\n+\n+\treturn connect_gethostbyname(hostcopy, _port, scope);\n+}\ndiff --git a/git-over-tls/connect.h b/git-over-tls/connect.h\nnew file mode 100644\nindex 0000000..90e36cd\n--- /dev/null\n+++ b/git-over-tls/connect.h\n@@ -0,0 +1,14 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _connect__h__included__\n+#define _connect__h__included__\n+\n+//Returns connected fd or dies.\n+int connect_host(const char* host, unsigned short port);\n+\n+#endif\ndiff --git a/git-over-tls/genkeypair.c b/git-over-tls/genkeypair.c\nnew file mode 100644\nindex 0000000..4f2142c\n--- /dev/null\n+++ b/git-over-tls/genkeypair.c\n@@ -0,0 +1,38 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+void write_cert(int server_mode);\n+\n+static void do_help()\n+{\n+\tprintf(\"gits-generate-keypair: User keypair generator.\\n\");\n+\tprintf(\"Command line options:\\n\");\n+\tprintf(\"--help\\n\");\n+\tprintf(\"\\tThis help\\n\");\n+\tprintf(\"\\n\");\n+\tprintf(\"Note: Keep generated keypair files private!\\n\");\n+\tprintf(\"Use gits-get-key-name to get public short\\n\");\n+\tprintf(\"representation of keypair for authorization.\\n\");\n+\tprintf(\"\\n\");\n+\tprintf(\"WARNING: Don't let keypairs to be tampered with!\\n\");\n+\tprintf(\"WARNING: Tampered keypairs may do very nasty things\\n\");\n+\tprintf(\"WARNING: if used.\\n\");\n+\texit(0);\n+}\n+\n+\n+int main(int argc, char **argv)\n+{\n+\tif (argc > 1 && !strcmp(argv[1], \"--help\"))\n+\t\tdo_help();\n+\twrite_cert(0);\n+\treturn 0;\n+}\ndiff --git a/git-over-tls/gensrpverifier.c b/git-over-tls/gensrpverifier.c\nnew file mode 100644\nindex 0000000..751854a\n--- /dev/null\n+++ b/git-over-tls/gensrpverifier.c\n@@ -0,0 +1,372 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"prompt.h\"\n+#include <stdio.h>\n+#include <string.h>\n+#include <gnutls/gnutls.h>\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <errno.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+static void do_help()\n+{\n+\tprintf(\"gits-generate-srp-verifier: Generate SRP verifiers for\\n\");\n+\tprintf(\"password authentication\\n\");\n+\tprintf(\"Command line:\\n\");\n+\tprintf(\"--help\\n\");\n+\tprintf(\"\\tThis help\\n\");\n+\tprintf(\"\\n\");\n+\tprintf(\"Note: Server needs SRP verifier in order to do password\\n\");\n+\tprintf(\"authentication. Usernames are assigned by repostiory\\n\");\n+\tprintf(\"hosting admin, just using arbitrary names doesn't work.\\n\");\n+\texit(0);\n+}\n+\n+\n+struct field\n+{\n+\tconst char *f_name;\n+\tconst char *f_generator;\n+\tconst char *f_prime;\n+};\n+\n+struct field fields[] = {\n+\t{\n+\t\"standard 1024 bit field\", \"2\",\n+\t\"Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE\"\n+\t\"3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Z\"\n+\t\"jl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ\"\n+\t},\n+\t{\n+\t\"standard 1536 bit field\", \"2\",\n+\t\"dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5\"\n+\t\"LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTa\"\n+\t\"u/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtm\"\n+\t\"cIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx\"\n+\t},\n+\t{\n+\t\"standard 2048 bit field\", \"2\",\n+\t\"2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJt\"\n+\t\"L8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL\"\n+\t\".NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nq\"\n+\t\"ha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXq\"\n+\t\"vp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUD\"\n+\t\"o/DQEyW.d4H.UIlzp\"\n+\t},\n+\t{NULL, NULL, NULL}\n+};\n+\n+\n+unsigned char xfact[16] = {\n+0x9D, 0x0F, 0x49, 0xD4,\n+0x73, 0x88, 0xC7, 0xFF,\n+0xFD, 0x24, 0x6A, 0x0F,\n+0x94, 0x78, 0x0C, 0x14\n+};\n+\n+unsigned char rnd[16] = {\n+0x00, 0x00, 0x00, 0x00,\n+0x00, 0x00, 0x00, 0x00,\n+0x00, 0x00, 0x00, 0x00,\n+0x00, 0x00, 0x00, 0x00\n+};\n+\n+static void add(unsigned char *res, unsigned char *a, unsigned char *b)\n+{\n+\tunsigned char carry = 0;\n+\tunsigned i;\n+\n+\tfor (i = 0; i < 16; i++) {\n+\t\tunsigned char newcarry = 0;\n+\n+\t\tif ((unsigned char)(a[i] + b[i]) < a[i])\n+\t\t\tnewcarry++;\n+\t\tres[i] = a[i] + b[i];\n+\t\tif (res[i] + carry < res[i])\n+\t\t\tnewcarry++;\n+\t\tres[i] += carry;\n+\t\tcarry = newcarry;\n+\t}\n+\twhile (carry) {\n+\t\tcarry = 159;\n+\n+\t\tfor (i = 0; i < 16; i++) {\n+\t\t\tint newcarry = 0;\n+\n+\t\t\tif ((unsigned char)(res[i] + carry) < res[i])\n+\t\t\t\tnewcarry++;\n+\t\t\tres[i] += carry;\n+\t\t\tcarry = newcarry;\n+\t\t}\n+\t}\n+\tfor (i = 1; i < 16; i++)\n+\t\tif (res[i] < 255)\n+\t\t\tgoto skip;\n+\n+\tif (res[0] > 0x60) {\n+\t\tfor (i = 1; i < 16; i++)\n+\t\t\tres[i] = 0;\n+\t\tres[0] -= 0x61;\n+\t}\n+skip:\n+\t;\n+}\n+\n+void update_xfact()\n+{\n+\tunsigned char xfact2[16];\n+\tunsigned char xfact4[16];\n+\tunsigned char xfact5[16];\n+\tadd(xfact2, xfact, xfact);\n+\tadd(xfact4, xfact2, xfact2);\n+\tadd(xfact5, xfact4, xfact);\n+\tmemcpy(xfact, xfact5, 16);\n+}\n+\n+void update_rnd(unsigned char ch)\n+{\n+\tunsigned char rndt[16];\n+\tunsigned i;\n+\tfor (i = 0; i < 8; i++) {\n+\t\tif ((ch >> i) % 2) {\n+\t\t\tadd(rndt, rnd, xfact);\n+\t\t\tmemcpy(rnd, rndt, 16);\n+\t\t}\n+\t\tupdate_xfact();\n+\t}\n+}\n+\n+void update_rnd_str(const char *ch)\n+{\n+\twhile (ch && *ch)\n+\t\tupdate_rnd((unsigned char)*(ch++));\n+}\n+\n+\n+void decode_element(gnutls_datum_t *decode, const char *encoded)\n+{\n+\tint s;\n+\tgnutls_datum_t _base64;\n+\tsize_t base64len, reslen, reslen2;\n+\n+\tbase64len = strlen(encoded);\n+\treslen2 = reslen = (3 * base64len + 1) / 4;\n+\n+\t_base64.data = (unsigned char*)encoded;\n+\t_base64.size = base64len;\n+\n+\tdecode->size = reslen;\n+\tdecode->data = xmalloc(reslen);\n+\ts = gnutls_srp_base64_decode(&_base64, (char*)decode->data, &reslen2);\n+\tif (s < 0)\n+\t\tdie(\"Unable to decode base64 data\");\n+\telse if (reslen != reslen2)\n+\t\tdie(\"Base64 dlength calculation incorrect. Calculated %lu, \"\n+\t\t\t\"got %lu\", (unsigned long)reslen, (unsigned long)reslen2);\n+}\n+\n+unsigned char *encode_element(gnutls_datum_t *data)\n+{\n+\tint s;\n+\tsize_t reslen2;\n+\tunsigned char *res;\n+\n+\treslen2 = (4 * data->size + 2) / 3;\n+\n+\tres = xmalloc(reslen2 + 1);\n+\ts = gnutls_srp_base64_encode(data, (char*)res, &reslen2);\n+\tif (s < 0)\n+\t\tdie(\"Unable to encode base64 data\");\n+\tres[reslen2] = '\\0';\n+\treturn res;\n+}\n+\n+char *generate_srp_line(const char *username,\n+\tconst char *password, const char *junk, struct field *field)\n+{\n+\tgnutls_datum_t salt;\n+\tgnutls_datum_t g;\n+\tgnutls_datum_t n;\n+\tgnutls_datum_t res;\n+\tint s;\n+\tchar *retline = NULL;\n+\tunsigned char *encoded_salt;\n+\tunsigned char *encoded_verifier;\n+\tupdate_rnd_str(username);\n+\tupdate_rnd_str(\":::::\");\n+\tupdate_rnd_str(junk);\n+\n+\tsalt.data = rnd;\n+\tsalt.size = 16;\n+\tdecode_element(&g, field->f_generator);\n+\tdecode_element(&n, field->f_prime);\n+\n+\ts = gnutls_srp_verifier(username, password, &salt, &g, &n, &res);\n+\tif (s < 0)\n+\t\tdie(\"Unable to generate SRP verifier: %s\",\n+\t\t\tgnutls_strerror(s));\n+\n+\tencoded_verifier = encode_element(&res);\n+\tencoded_salt = encode_element(&salt);\n+\n+\tretline = xmalloc(5 + strlen(username) +\n+\t\tstrlen((char*)encoded_salt) +\n+\t\tstrlen((char*)encoded_verifier) +\n+\t\tstrlen(field->f_generator) +\n+\t\tstrlen(field->f_prime));\n+\tretline[0] = '\\0';\n+\tstrcat(retline, username);\n+\tstrcat(retline, \":\");\n+\tstrcat(retline, (char*)encoded_salt);\n+\tstrcat(retline, \":\");\n+\tstrcat(retline, (char*)encoded_verifier);\n+\tstrcat(retline, \":\");\n+\tstrcat(retline, field->f_generator);\n+\tstrcat(retline, \":\");\n+\tstrcat(retline, field->f_prime);\n+\n+\tfree(encoded_verifier);\n+\tfree(encoded_salt);\n+\tfree(res.data);\n+\tfree(g.data);\n+\tfree(n.data);\n+\n+\treturn retline;\n+}\n+\n+#define LINELEN 69\n+\n+static void flush_to_file(FILE *out, const char *srpline)\n+{\n+\tchar linebuffer[LINELEN + 2];\n+\n+\tlinebuffer[LINELEN] = '\\\\';\n+\tlinebuffer[LINELEN + 1] = '\\0';\n+\n+\twhile (*srpline) {\n+\t\tsize_t r;\n+\t\tstrncpy(linebuffer, srpline, LINELEN);\n+\t\tr = strlen(srpline);\n+\t\tif (r == LINELEN)\n+\t\t\tlinebuffer[LINELEN] = '\\0';\n+\t\tif (r <= LINELEN)\n+\t\t\tsrpline += r;\n+\t\telse\n+\t\t\tsrpline += LINELEN;\n+\t\tfprintf(out, \"%s\\n\", linebuffer);\n+\t}\n+}\n+\n+int fill_rnd()\n+{\n+\tint fd;\n+\tint fill = 0;\n+\n+\tfd = open(\"/dev/urandom\", O_RDONLY);\n+\tif (fd < 0)\n+\t\treturn 0;\n+\n+\twhile (fill < 16) {\n+\t\tssize_t r;\n+\t\tr = read(fd, rnd + fill, 16 - fill);\n+\t\tif (r < 0 && errno != EINTR && errno != EAGAIN) {\n+\t\t\tclose(fd);\n+\t\t\treturn 0;\n+\t\t} else if (r == 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn 0;\n+\t\t} else {\n+\t\t\tfill += r;\n+\t\t}\n+\t}\n+\tclose(fd);\n+\treturn 1;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tint idx, midx = 0;\n+\tchar *username = NULL;\n+\tchar *password = NULL;\n+\tchar *password2 = NULL;\n+\tchar *junk = NULL;\n+\tchar *field = NULL;\n+\tchar *file = NULL;\n+\tchar *ans = NULL;\n+\tchar *end = NULL;\n+\tFILE *filp = stdout;\n+\n+\tif (argc > 1 && !strcmp(argv[1], \"--help\"))\n+\t\tdo_help();\n+\n+username_again:\n+\tfree(username);\n+\tusername = prompt_string(\"Enter username\", 0);\n+\tif (!*username) {\n+\t\tfprintf(stderr, \"Error: Bad username\\n\");\n+\t\tgoto username_again;\n+\t}\n+\n+\tif (fill_rnd())\n+\t\tgoto no_junk_prompt;\n+junk_again:\n+\tfree(junk);\n+\tjunk = prompt_string(\"Enter some garbage from keyboard (min 32 \"\n+\t\t\"chars)\", 1);\n+\tif (strlen(junk) < 32) {\n+\t\tfprintf(stderr, \"Error: Garbage needs to be at least \"\n+\t\t\t\"32 characters\\n\");\n+\t\tgoto junk_again;\n+\t}\n+no_junk_prompt:\n+\n+passwords_again:\n+\tfree(password);\n+\tfree(password2);\n+\tpassword = prompt_string(\"Enter password\", 1);\n+\tpassword2 = prompt_string(\"Enter password again\", 1);\n+\tif (strcmp(password, password2)) {\n+\t\tfprintf(stderr, \"Error: Passwords don't match\\n\");\n+\t\tgoto passwords_again;\n+\t}\n+\n+field_again:\n+\tfree(field);\n+\tfor (midx = 0; fields[midx].f_name; midx++) {\n+\t\tprintf(\"%i) %s\\n\", midx + 1, fields[midx].f_name);\n+\t}\n+\tfield = prompt_string(\"Pick field\", 0);\n+\tidx = (int)strtoul(field, &end, 10) - 1;\n+\tif (idx < 0 || idx >= midx || !*field || *end) {\n+\t\tprintf(\"%i %i %i %i\\n\", idx, midx, *field, *end);\n+\t\tfprintf(stderr, \"Error: Invalid choice\\n\");\n+\t\tgoto field_again;\n+\t}\n+\n+file_again:\n+\tfile = prompt_string(\"Filename to save as (enter for dump to \"\n+\t\t\"terminal)\", 0);\n+\tif (*file) {\n+\t\tfilp = fopen(file, \"w\");\n+\t\tif (!filp) {\n+\t\t\tfprintf(stderr, \"Can't open \\\"%s\\\"\\n\", file);\n+\t\t\tgoto file_again;\n+\t\t}\n+\t}\n+\n+\tans = generate_srp_line(username, password, junk, fields + idx);\n+\tflush_to_file(filp, ans);\n+\tif (filp != stdout)\n+\t\tfclose(filp);\n+\treturn 0;\n+}\ndiff --git a/git-over-tls/getkeyid.c b/git-over-tls/getkeyid.c\nnew file mode 100644\nindex 0000000..091e60b\n--- /dev/null\n+++ b/git-over-tls/getkeyid.c\n@@ -0,0 +1,118 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"certificate.h\"\n+#include \"home.h\"\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <limits.h>\n+#include <string.h>\n+#include <gnutls/openpgp.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+\n+static void do_help()\n+{\n+\tprintf(\"gits-get-key-name: Get name for keypair or hostkey.\\n\");\n+\tprintf(\"Command line options:\\n\");\n+\tprintf(\"--help\\n\");\n+\tprintf(\"\\tThis help\\n\");\n+\tprintf(\"<keyfile>\\n\");\n+\tprintf(\"\\tRead key file <keyfile>. Name must contain at least\\n\");\n+\tprintf(\"\\tone '/'\\n\");\n+\tprintf(\"<keyname>\\n\");\n+\tprintf(\"\\tRead key named <keyname>. Name must not contain\\n\");\n+\tprintf(\"\\t'/'\\n\");\n+\tprintf(\"\\n\");\n+\tprintf(\"Note: These key names are used in hostkey database and\\n\");\n+\tprintf(\"as user names seen by authorization program.\\n\");\n+\texit(0);\n+}\n+\n+\n+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */\n+#define KEYBUF 128\n+\n+int main(int argc, char **argv)\n+{\n+\tstruct certificate certificate;\n+\tgnutls_openpgp_crt_t cert;\n+\tchar filename[PATH_MAX + 1];\n+\tunsigned char key[KEYBUF];\n+\tint s;\n+\tunsigned vout;\n+\n+\tif (argc != 2) {\n+\t\tfprintf(stderr, \"syntax: %s <keyname>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s <keyfile>\\n\", argv[0]);\n+\t\treturn 1;\n+\t}\n+\n+\tif (!strcmp(argv[1], \"--help\"))\n+\t\tdo_help();\n+\n+\tif (strchr(argv[1], '/'))\n+\t\ts = snprintf(filename, PATH_MAX + 1, \"%s\", argv[1]);\n+\telse\n+\t\ts = snprintf(filename, PATH_MAX + 1, \"%s/.gits/keys/%s\",\n+\t\t\tget_home(), argv[1]);\n+\tif (s < 0 || s > PATH_MAX)\n+\t\tdie(\"Insanely long homedir/keyname\");\n+\n+\ts = gnutls_global_init();\n+\tif (s < 0)\n+\t\tdie(\"Can't initialize GnuTLS: %s\",\n+\t\t\tgnutls_strerror(s));\n+\n+\n+\tcertificate = parse_certificate(filename, &s);\n+\tif (s) {\n+\t\tif (s == CERTERR_NOCERT)\n+\t\t\tdie(\"Can't find key %s\", filename);\n+\t\telse if (s == CERTERR_CANTREAD)\n+\t\t\tdie_errno(\"Can't read key\");\n+\t\telse\n+\t\t\tdie(\"Can't parse key: %s\",\n+\t\t\t\tcert_parse_strerr(s));\n+\t}\n+\n+\ts = gnutls_openpgp_crt_init(&cert);\n+\tif (s < 0)\n+\t\tdie(\"Can't allocate space for key: %s\",\n+\t\t\tgnutls_strerror(s));\n+\n+\ts = gnutls_openpgp_crt_import(cert, &certificate.public_key,\n+\t\tGNUTLS_OPENPGP_FMT_RAW);\n+\tif (s < 0)\n+\t\tdie(\"Bad key: %s\", gnutls_strerror(s));\n+\n+\ts = gnutls_openpgp_crt_verify_self(cert, 0, &vout);\n+\tif (s < 0)\n+\t\tdie(\"Bad key: %s\", gnutls_strerror(s));\n+\tif (vout)\n+\t\tdie(\"Bad key: Validation failed\");\n+\n+\tvout = KEYBUF;\n+\ts = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);\n+\tif (s < 0)\n+\t\tdie(\"Bad key: %s\", gnutls_strerror(s));\n+\n+\tgnutls_openpgp_crt_deinit(cert);\n+\n+\tprintf(\"openpgp-\");\n+\tfor (s = 0; s < (int)vout; s++)\n+\t\tprintf(\"%02x\", key[s]);\n+\tprintf(\"\\n\");\n+\treturn 0;\n+}\ndiff --git a/git-over-tls/gits-send-special-command b/git-over-tls/gits-send-special-command\nnew file mode 100755\nindex 0000000..ade530d\n--- /dev/null\n+++ b/git-over-tls/gits-send-special-command\n@@ -0,0 +1,22 @@\n+#!/bin/sh\n+#\n+# Copyright (C) Ilari Liusvaara 2009\n+#\n+# This code is free software; you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License version 2 as\n+# published by the Free Software Foundation.\n+#\n+\n+if test \"x${1}\" == \"x--help\"\n+then\n+\techo \"gits-send-special-command: Send special command to \"\n+\techo \"server\"\n+\techo \"command line:\"\n+\techo \"--help\"\n+\techo -e \"\\x09This help\"\n+\techo \"<service> <URL>\"\n+\techo -e \"\\x09Send request for <service> to specified <URL>.\"\n+\texit 0\n+fi\n+\n+git-remote-gits --service=$1 $2\ndiff --git a/git-over-tls/home.c b/git-over-tls/home.c\nnew file mode 100644\nindex 0000000..b41dbfa\n--- /dev/null\n+++ b/git-over-tls/home.c\n@@ -0,0 +1,47 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"home.h\"\n+#include <string.h>\n+#include <stdlib.h>\n+#include <pwd.h>\n+#include <unistd.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+const char *get_home()\n+{\n+\tstatic char *home = NULL;\n+\tconst char *tmpans;\n+#ifndef WIN32\n+\tstruct passwd *user;\n+#endif\n+\n+\tif (home)\n+\t\treturn home;\n+\n+\tif (getenv(\"HOME\")) {\n+\t\ttmpans = getenv(\"HOME\");\n+\t\tgoto got_it;\n+\t}\n+\n+#ifndef WIN32\n+\tuser = getpwuid(getuid());\n+\tif (user && user->pw_dir) {\n+\t\ttmpans = user->pw_dir;\n+\t\tgoto got_it;\n+\t}\n+#endif\n+\n+\tdie(\"Can't obtain home directory of current user\");\n+got_it:\n+\thome = xstrdup(tmpans);\n+\treturn home;\n+}\ndiff --git a/git-over-tls/home.h b/git-over-tls/home.h\nnew file mode 100644\nindex 0000000..133ee78\n--- /dev/null\n+++ b/git-over-tls/home.h\n@@ -0,0 +1,13 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _home__h__included__\n+#define _home__h__included__\n+\n+const char *get_home();\n+\n+#endif\ndiff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c\nnew file mode 100644\nindex 0000000..28df0e5\n--- /dev/null\n+++ b/git-over-tls/hostkey.c\n@@ -0,0 +1,116 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"hostkey.h\"\n+#include \"home.h\"\n+#include <stdio.h>\n+#include <limits.h>\n+#include <string.h>\n+#include <gnutls/openpgp.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */\n+#define KEYBUF 128\n+#define MAXLINE 2048\n+\n+void check_hostkey(gnutls_session_t session, const char *hostname)\n+{\n+\tconst gnutls_datum_t *certificate = NULL;\n+\tunsigned int cert_size = 0;\n+\tgnutls_openpgp_crt_t cert;\n+\tint s;\n+\tunsigned int vout;\n+\tunsigned char key[KEYBUF];\n+\tFILE *hostfile;\n+\tchar hostfilepath[PATH_MAX + 1];\n+\tchar linebuffer[MAXLINE];\n+\n+\tcertificate = gnutls_certificate_get_peers(session, &cert_size);\n+\tif (!certificate)\n+\t\tdie(\"Server didn't send a hostkey\");\n+\n+\ts = gnutls_openpgp_crt_init(&cert);\n+\tif (s < 0)\n+\t\tdie(\"Can't allocate space for hostkey: %s\",\n+\t\t\tgnutls_strerror(s));\n+\n+\ts = gnutls_openpgp_crt_import(cert, certificate,\n+\t\tGNUTLS_OPENPGP_FMT_RAW);\n+\tif (s < 0)\n+\t\tdie(\"Server sent bad hostkey: %s\", gnutls_strerror(s));\n+\n+\t/* Defend against subkey attack. */\n+\ts = gnutls_openpgp_crt_get_subkey_count(cert);\n+\tif (s != 0)\n+\t\tdie(\"Server sent bad hostkey: Subkeys are not allowed\");\n+\n+\ts = gnutls_openpgp_crt_verify_self(cert, 0, &vout);\n+\tif (s < 0)\n+\t\tdie(\"Server sent bad hostkey: %s\", gnutls_strerror(s));\n+\tif (vout)\n+\t\tdie(\"Server sent bad hostkey: Validation failed\");\n+\n+\tvout = KEYBUF;\n+\ts = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);\n+\tif (s < 0)\n+\t\tdie(\"Server sent bad hostkey: %s\", gnutls_strerror(s));\n+\n+\tgnutls_openpgp_crt_deinit(cert);\n+\n+\ts = snprintf(hostfilepath, PATH_MAX + 1, \"%s/.gits/hostkeys\",\n+\t\tget_home());\n+\tif (s < 0 || s > PATH_MAX)\n+\t\tdie(\"Home directory path insanely long\");\n+\n+\thostfile = fopen(hostfilepath, \"r\");\n+\tif (!hostfile)\n+\t\tdie_errno(\"Can't open .gits/hostkeys\");\n+\n+\twhile (fgets(linebuffer, MAXLINE - 2, hostfile)) {\n+\t\tchar *split;\n+\t\tif (!*linebuffer || *linebuffer == '#')\n+\t\t\tcontinue;\n+\t\tif (linebuffer[strlen(linebuffer) - 1] == '\\n')\n+\t\t\tlinebuffer[strlen(linebuffer) - 1] = '\\0';\n+\n+\t\tsplit = strchr(linebuffer, ' ');\n+\t\tif (!split)\n+\t\t\tcontinue;\n+\t\t*split = '\\0';\n+\t\tif (strcmp(linebuffer, hostname))\n+\t\t\tcontinue;\n+\n+\t\t/*\n+\t\t * Be nice to users and strip this in case it gets\n+\t\t * retained from key id calculator.\n+\t\t */\n+\t\tif (!strncmp(split + 1, \"openpgp-\", 8))\n+\t\t\tsplit += 8;\n+\n+\t\tfor (s = 0; s < vout; s++) {\n+\t\t\tchar buffer[3];\n+\t\t\tsprintf(buffer, \"%02x\", (int)key[s]);\n+\t\t\tif (buffer[0] != split[2 * s + 1])\n+\t\t\t\tdie(\"HOST KEY MISMATCH FOR HOST %s!\",\n+\t\t\t\t\thostname);\n+\t\t\tif (buffer[1] != split[2 * s + 2])\n+\t\t\t\tdie(\"HOST KEY MISMATCH FOR HOST %s!\",\n+\t\t\t\t\thostname);\n+\t\t}\n+\t\tif (split[2 * vout + 1])\n+\t\t\tdie(\"HOST KEY MISMATCH FOR HOST %s!\",\n+\t\t\t\thostname);\n+\t\tgoto ok;\n+\t}\n+\tdie(\"Hostkey for %s not found in hostkeys list\", hostname);\n+ok:\n+\tfclose(hostfile);\n+}\ndiff --git a/git-over-tls/hostkey.h b/git-over-tls/hostkey.h\nnew file mode 100644\nindex 0000000..c0e7dfe\n--- /dev/null\n+++ b/git-over-tls/hostkey.h\n@@ -0,0 +1,15 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _hostkey__h__included__\n+#define _hostkey__h__included__\n+\n+#include <gnutls/gnutls.h>\n+\n+void check_hostkey(gnutls_session_t session, const char *hostname);\n+\n+#endif\ndiff --git a/git-over-tls/hostkeymanager.c b/git-over-tls/hostkeymanager.c\nnew file mode 100644\nindex 0000000..2df09e0\n--- /dev/null\n+++ b/git-over-tls/hostkeymanager.c\n@@ -0,0 +1,305 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"home.h\"\n+#include <stdio.h>\n+#include <unistd.h>\n+#include <errno.h>\n+#include <string.h>\n+#include <sys/stat.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+#define BUFSIZE 8192\n+\n+static void do_help()\n+{\n+\tprintf(\"gits-hostkey: Add, update or delete entries in hostkey.\\n\");\n+\tprintf(\"database.\\n\");\n+\tprintf(\"Command line:\\n\");\n+\tprintf(\"--help\\n\");\n+\tprintf(\"\\tThis help\\n\");\n+\tprintf(\"list\\n\");\n+\tprintf(\"\\tList hosts known and their keys\\n\");\n+\tprintf(\"add <host> <key>\\n\");\n+\tprintf(\"\\tAdd <host> to database with key <key>\\n\");\n+\tprintf(\"update <host> <key>\\n\");\n+\tprintf(\"\\tUpdate <host> to database to use key <key>\\n\");\n+\tprintf(\"delete <host>\\n\");\n+\tprintf(\"\\tDelete key for <host>\\n\");\n+\texit(0);\n+}\n+\n+\n+struct hostkey\n+{\n+\t/* Hostname. NULL if not valid line. */\n+\tchar *h_hostname;\n+\t/* Hostkey. NULL if not valid line. */\n+\tchar *h_hostkey;\n+\t/* Line. Non-NULL if hostname/hoskey is NULL. */\n+\tchar *h_line;\n+\t/* Next line. */\n+\tstruct hostkey *h_next;\n+};\n+\n+struct hostkey *first_hostkey = NULL;\n+struct hostkey *last_hostkey = NULL;\n+\n+int ensure_leading_directories()\n+{\n+\tstruct stat s;\n+\tchar fsobj[BUFSIZE];\n+\tint r;\n+\n+\tsprintf(fsobj, \"%s/.gits\", get_home());\n+\tr = stat(fsobj, &s);\n+\tif (r < 0 && errno != ENOENT)\n+\t\tdie_errno(\"Stat $HOME/.gits\");\n+\telse if (r == 0 && !S_ISDIR(s.st_mode))\n+\t\tdie(\"$HOME/.gits exists but is not a directory\");\n+\telse if (r < 0) {\n+\t\t/* Need to create it. */\n+\t\tif (mkdir(fsobj, 0700) < 0)\n+\t\t\tdie_errno(\"Create $HOME/.gits failed\");\n+\t}\n+\t/* Otherwise, r == 0 && S_ISDIR(s), which is OK. */\n+\tsprintf(fsobj, \"%s/.gits/hostkeys\", get_home());\n+\tr = stat(fsobj, &s);\n+\tif (r < 0 && errno != ENOENT)\n+\t\tdie_errno(\"Stat $HOME/.gits/hostkeys\");\n+\telse if (r == 0 && !S_ISREG(s.st_mode))\n+\t\tdie(\"$HOME/.gits/hostkeys exists but is not a file\");\n+\t/* Doesn't exist or a regular file. OK. */\n+\treturn (r == 0) ? 1 : 0;\n+}\n+\n+void load_hostkeys()\n+{\n+\tchar fsobj[BUFSIZE];\n+\tchar linebuf[BUFSIZE];\n+\tFILE *filp;\n+\n+\tif (!ensure_leading_directories())\n+\t\treturn;\n+\n+\tsprintf(fsobj, \"%s/.gits/hostkeys\", get_home());\n+\tfilp = fopen(fsobj, \"r\");\n+\tif (filp == NULL)\n+\t\tdie(\"Can't open $HOME/.gits/hostkeys\");\n+\n+\twhile (fgets(linebuf, BUFSIZE - 2, filp)) {\n+\t\tstruct hostkey *h;\n+\t\tchar *split;\n+\t\th = xmalloc(sizeof(struct hostkey));\n+\t\th->h_next = NULL;\n+\t\th->h_line = xstrdup(linebuf);\n+\n+\t\tif (*h->h_line && h->h_line[strlen(h->h_line) - 1] == '\\n')\n+\t\t\th->h_line[strlen(h->h_line) - 1] = '\\0';\n+\n+\t\tif (!*h->h_line || h->h_line[0] == '#')\n+\t\t\tgoto not_valid;\n+\t\tsplit = strchr(h->h_line, ' ');\n+\t\tif (!split)\n+\t\t\tgoto not_valid;\n+\n+\t\t*split = '\\0';\n+\t\th->h_hostname = h->h_line;\n+\t\th->h_hostkey = xstrdup(split + 1);\n+\t\th->h_line = NULL;\n+not_valid:\n+\t\tif (last_hostkey)\n+\t\t\tlast_hostkey = last_hostkey->h_next = h;\n+\t\telse\n+\t\t\tfirst_hostkey = last_hostkey = h;\n+\t}\n+\tif (!feof(filp))\n+\t\tdie(\"Error reading $HOME/.gits/hostkeys\");\n+\tfclose(filp);\n+}\n+\n+void save_hostkeys()\n+{\n+\tchar fsobj[BUFSIZE];\n+\tchar fsobj2[BUFSIZE];\n+\tFILE *filp;\n+\tstruct hostkey *host;\n+\n+\tsprintf(fsobj, \"%s/.gits/hostkeys.tmp\", get_home());\n+\tsprintf(fsobj2, \"%s/.gits/hostkeys\", get_home());\n+\tfilp = fopen(fsobj, \"w\");\n+\tif (filp == NULL)\n+\t\tdie(\"Can't open $HOME/.gits/hostkeys.tmp\");\n+\n+\tfor (host = first_hostkey; host; host = host->h_next) {\n+\t\tif (host->h_line) {\n+\t\t\tif (fprintf(filp, \"%s\\n\", host->h_line) < 0)\n+\t\t\t\tdie(\"hostkeys write error\");\n+\t\t} else {\n+\t\t\tif (fprintf(filp, \"%s %s\\n\", host->h_hostname,\n+\t\t\t\thost->h_hostkey) < 0)\n+\t\t\t\tdie(\"hostkeys write error\");\n+\t\t}\n+\t}\n+\n+\tif (fclose(filp) < 0)\n+\t\tdie(\"Hostkeys write error\");\n+\n+\tif (rename(fsobj, fsobj2) < 0)\n+\t\tdie(\"Error renaming hostkeys\");\n+}\n+\n+void list_hostkeys()\n+{\n+\tstruct hostkey *host;\n+\tsize_t longest_hostname = 0;\n+\n+\tfor (host = first_hostkey; host; host = host->h_next) {\n+\t\tsize_t hostnamelen = 0;\n+\t\thostnamelen = host->h_hostname ? strlen(host->h_hostname) : 0;\n+\t\tif (longest_hostname < hostnamelen)\n+\t\t\tlongest_hostname = hostnamelen;\n+\t}\n+\n+\tfor (host = first_hostkey; host; host = host->h_next) {\n+\t\tsize_t pad;\n+\t\tsize_t i;\n+\t\tif (!host->h_hostname)\n+\t\t\tcontinue;\n+\t\tpad = longest_hostname + 1 - strlen(host->h_hostname);\n+\t\tprintf(\"%s\", host->h_hostname);\n+\t\tfor (i = 0; i < pad; i++)\n+\t\t\tprintf(\" \");\n+\t\tprintf(\"%s\\n\", host->h_hostkey);\n+\t}\n+}\n+\n+void add_hostkey(const char *host, const char *key)\n+{\n+\tstruct hostkey *h;\n+\n+\tfor (h = first_hostkey; h; h = h->h_next) {\n+\t\tif (!h->h_hostname)\n+\t\t\tcontinue;\n+\t\tif (!strcmp(h->h_hostname, host))\n+\t\t\tdie(\"Host %s already in hostkeys\", host);\n+\t}\n+\n+\th = xmalloc(sizeof(struct hostkey));\n+\th->h_next = NULL;\n+\th->h_line = NULL;\n+\th->h_hostname = xstrdup(host);\n+\th->h_hostkey = xstrdup(key);\n+\n+\tif (last_hostkey)\n+\t\tlast_hostkey = last_hostkey->h_next = h;\n+\telse\n+\t\tfirst_hostkey = last_hostkey = h;\n+}\n+\n+void update_hostkey(const char *host, const char *key)\n+{\n+\tstruct hostkey *h;\n+\n+\tfor (h = first_hostkey; h; h = h->h_next) {\n+\t\tif (!h->h_hostname)\n+\t\t\tcontinue;\n+\t\tif (!strcmp(h->h_hostname, host)) {\n+\t\t\th->h_hostkey = xstrdup(key);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\tdie(\"Host %s not found in hostkeys\", host);\n+}\n+\n+void delete_hostkey(const char *host)\n+{\n+\tstruct hostkey *h;\n+\tstruct hostkey *prev = NULL;\n+\n+\tfor (h = first_hostkey; h; h = h->h_next) {\n+\t\tif (!h->h_hostname)\n+\t\t\tcontinue;\n+\t\tif (!strcmp(h->h_hostname, host)) {\n+\t\t\tif (prev)\n+\t\t\t\tprev->h_next = h->h_next;\n+\t\t\telse\n+\t\t\t\tfirst_hostkey = h->h_next;\n+\t\t\tfree(h);\n+\t\t\treturn;\n+\t\t}\n+\t\tprev = h;\n+\t}\n+\tdie(\"Host %s not found in hostkeys\", host);\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tif (argc < 2) {\n+\t\tfprintf(stderr, \"syntax: %s list\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s add <host> <key>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s update <host> <key>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s delete <host>\\n\", argv[0]);\n+\t\treturn 1;\n+\t}\n+\tif (!strcmp(argv[1], \"--help\"))\n+\t\tdo_help();\n+\n+\tif (!strcmp(argv[1], \"list\")) {\n+\t\tif (argc != 2) {\n+\t\t\tfprintf(stderr, \"syntax: %s list\\n\", argv[0]);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tload_hostkeys();\n+\t\tlist_hostkeys();\n+\t\treturn 0;\n+\t} else if (!strcmp(argv[1], \"add\")) {\n+\t\tif (argc != 4) {\n+\t\t\tfprintf(stderr, \"syntax: %s add <host> <key>\\n\",\n+\t\t\t\targv[0]);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tload_hostkeys();\n+\t\tadd_hostkey(argv[2], argv[3]);\n+\t\tsave_hostkeys();\n+\t\treturn 0;\n+\t} else if (!strcmp(argv[1], \"update\")) {\n+\t\tif (argc != 4) {\n+\t\t\tfprintf(stderr, \"syntax: %s update <host> <key>\\n\",\n+\t\t\t\targv[0]);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tload_hostkeys();\n+\t\tupdate_hostkey(argv[2], argv[3]);\n+\t\tsave_hostkeys();\n+\t\treturn 0;\n+\t} else if (!strcmp(argv[1], \"delete\")) {\n+\t\tif (argc != 3) {\n+\t\t\tfprintf(stderr, \"syntax: %s delete <host>\\n\",\n+\t\t\t\targv[0]);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tload_hostkeys();\n+\t\tdelete_hostkey(argv[2]);\n+\t\tsave_hostkeys();\n+\t\treturn 0;\n+\t} else {\n+\t\tfprintf(stderr, \"syntax: %s list\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s add <host> <key>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s update <host> <key>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s delete <host>\\n\", argv[0]);\n+\t\treturn 1;\n+\t}\n+}\ndiff --git a/git-over-tls/keypairs.c b/git-over-tls/keypairs.c\nnew file mode 100644\nindex 0000000..cc77217\n--- /dev/null\n+++ b/git-over-tls/keypairs.c\n@@ -0,0 +1,60 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"keypairs.h\"\n+#include \"home.h\"\n+#include \"certificate.h\"\n+#include <stdio.h>\n+#include <limits.h>\n+#include <gnutls/openpgp.h>\n+#include <errno.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+int select_keypair_int(gnutls_certificate_credentials_t creds,\n+\tconst char *username)\n+{\n+\tchar keypath[PATH_MAX + 1];\n+\tconst char *home;\n+\tstruct certificate cert;\n+\tint r;\n+\n+\thome = get_home();\n+\n+\tr = snprintf(keypath, PATH_MAX + 1, \"%s/.gits/keys/%s\", home,\n+\t\tusername);\n+\tif (r < 0 || r > PATH_MAX) {\n+\t\tdie(\"Username too long\");\n+\t}\n+\n+\tcert = parse_certificate(keypath, &r);\n+\tif (r) {\n+\t\tif (r == CERTERR_NOCERT)\n+\t\t\treturn -1;\n+\t\tif (r == CERTERR_CANTREAD)\n+\t\t\tdie_errno(\"Can't read keypair\");\n+\t\telse\n+\t\t\tdie(\"Can't read keypair: %s\",\n+\t\t\t\tcert_parse_strerr(r));\n+\t}\n+\n+\tr = gnutls_certificate_set_openpgp_keyring_mem(creds,\n+\t\tcert.public_key.data, cert.public_key.size,\n+\t\tGNUTLS_OPENPGP_FMT_RAW);\n+\tif (r < 0)\n+\t\tdie(\"Can't load public key: %s\", gnutls_strerror(r));\n+\n+\tr = gnutls_certificate_set_openpgp_key_mem(creds, &cert.public_key,\n+\t\t&cert.private_key, GNUTLS_OPENPGP_FMT_RAW);\n+\tif (r < 0)\n+\t\tdie(\"Can't load keypair: %s\", gnutls_strerror(r));\n+\n+\treturn 0;\n+}\ndiff --git a/git-over-tls/keypairs.h b/git-over-tls/keypairs.h\nnew file mode 100644\nindex 0000000..11f4ef7\n--- /dev/null\n+++ b/git-over-tls/keypairs.h\n@@ -0,0 +1,16 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _keypairs__h__included__\n+#define _keypairs__h__included__\n+\n+#include <gnutls/openpgp.h>\n+\n+int select_keypair_int(gnutls_certificate_credentials_t creds,\n+\tconst char *username);\n+\n+#endif\n-- \n1.6.6.102.gd6f8f.dirty\n"},{"id":"131469","messageId":"1263388786-6880-3-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"22200","inReplyTo":"1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of 2)","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T13:19:46Z","receivedAt":"2010-01-13T13:19:46Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n git-over-tls/main.c                       |  460 ++++++++++\n git-over-tls/{home.h => misc.c}           |   12 +-\n git-over-tls/{keypairs.h => misc.h}       |   21 +-\n git-over-tls/mkcert.c                     |  507 +++++++++++\n git-over-tls/prompt.c                     |  100 +++\n git-over-tls/prompt.h                     |   18 +\n git-over-tls/srp_askpass.c                |   90 ++\n git-over-tls/{hostkey.h => srp_askpass.h} |    9 +-\n git-over-tls/user.c                       | 1384 +++++++++++++++++++++++++++++\n git-over-tls/user.h                       |  357 ++++++++\n 10 files changed, 2943 insertions(+), 15 deletions(-)\n create mode 100644 git-over-tls/main.c\n copy git-over-tls/{home.h => misc.c} (64%)\n copy git-over-tls/{keypairs.h => misc.h} (50%)\n create mode 100644 git-over-tls/mkcert.c\n create mode 100644 git-over-tls/prompt.c\n create mode 100644 git-over-tls/prompt.h\n create mode 100644 git-over-tls/srp_askpass.c\n copy git-over-tls/{hostkey.h => srp_askpass.h} (59%)\n create mode 100644 git-over-tls/user.c\n create mode 100644 git-over-tls/user.h\n\ndiff --git a/git-over-tls/main.c b/git-over-tls/main.c\nnew file mode 100644\nindex 0000000..a3c8f51\n--- /dev/null\n+++ b/git-over-tls/main.c\n@@ -0,0 +1,460 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"user.h\"\n+#include \"srp_askpass.h\"\n+#include \"keypairs.h\"\n+#include \"hostkey.h\"\n+#include \"connect.h\"\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <gnutls/gnutls.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+struct parsed_addr\n+{\n+\tchar *protocol;\t\t/* Protocol part */\n+\tchar *user;\t\t/* User part, NULL if no user */\n+\tchar *host;\t\t/* Hostname */\n+\tchar *uhost;\t\t/* Unique host + port */\n+\tchar *port;\t\t/* Port as string, NULL if no port */\n+\tchar *path;\t\t/* Path part. */\n+\tchar *vhost_header;\t/* vhost header to send */\n+\tunsigned short _port;\t/* Port as numeric. */\n+};\n+\n+char *copy_alloc(const char *str, size_t len)\n+{\n+\tchar *copy;\n+\n+\tcopy = xmalloc(len + 1);\n+\tcopy[len] = 0;\n+\tstrncpy(copy, str, len);\n+\treturn copy;\n+}\n+\n+void append_uniq_address(char* buffer, struct parsed_addr* _addr)\n+{\n+\tif (strchr(_addr->host, ':'))\n+\t\tstrcat(buffer, \"[\");\n+\tstrcat(buffer, _addr->host);\n+\tif (strchr(_addr->host, ':'))\n+\t\tstrcat(buffer, \"]\");\n+\tif (_addr->port) {\n+\t\tstrcat(buffer, \":\");\n+\t\tstrcat(buffer, _addr->port);\n+\t}\n+}\n+\n+struct parsed_addr parse_address(const char *addr)\n+{\n+\tstruct parsed_addr _addr;\n+\tconst char *proto_end;\n+\tconst char *path_start;\n+\tconst char *uhp_start;\n+\tconst char *uhp_delim;\n+\tconst char *orig_addr = addr;\n+\tsize_t addrlen;\n+\n+\taddrlen = strlen(addr);\n+\n+\tproto_end = strchr(addr, ':');\n+\tif (!proto_end)\n+\t\tgoto bad;\n+\n+\t_addr.protocol = copy_alloc(addr, proto_end - addr);\n+\tif (strncmp(proto_end, \"://\", 3))\n+\t\tgoto bad;\n+\n+\tuhp_start = proto_end + 3;\n+\n+\t/* Figure out the user if any. */\n+\tuhp_delim = strpbrk(uhp_start, \"@[:/\");\n+\n+\tif (*uhp_delim == '@') {\n+\t\t_addr.user = copy_alloc(uhp_start,\n+\t\t\tuhp_delim - uhp_start);\n+\t\tuhp_start = uhp_delim + 1;\n+\t} else {\n+\t\t_addr.user = NULL;\n+\t}\n+\n+\t/* Figure out host. */\n+\tif (*uhp_start == '[') {\n+\t\tuhp_delim = strpbrk(uhp_start, \"]\");\n+\t\tif (uhp_delim) {\n+\t\t\t_addr.host = copy_alloc(uhp_start + 1,\n+\t\t\t\tuhp_delim - uhp_start - 1);\n+\t\t\tif (uhp_delim[1] != ':' && uhp_delim[1] != '/')\n+\t\t\t\tgoto bad;\n+\t\t\tuhp_start = uhp_delim + 1;\n+\t\t} else\n+\t\t\tgoto bad;\n+\t} else {\n+\t\tuhp_delim = strpbrk(uhp_start, \"[:/\");\n+\t\tif (*uhp_delim == '[')\n+\t\t\tgoto bad;\n+\t\t_addr.host = copy_alloc(uhp_start, uhp_delim - uhp_start);\n+\t\tuhp_start = uhp_delim;\n+\t}\n+\n+\tpath_start = strchr(uhp_start, '/');\n+\tif (!path_start)\n+\t\tgoto bad;\n+\n+\t_addr.path = copy_alloc(path_start, addrlen - (path_start - addr));\n+\n+\tif (*uhp_start == ':')\n+\t\t_addr.port = copy_alloc(uhp_start + 1,\n+\t\t\tpath_start - uhp_start - 1);\n+\telse\n+\t\t_addr.port = NULL;\n+\n+\tif (!*_addr.host)\n+\t\tgoto bad;\n+\n+\tif (strcmp(_addr.protocol, \"gits\") && strcmp(_addr.protocol, \"tls\") &&\n+\t\tstrcmp(_addr.protocol, \"git\")) {\n+\t\tdie(\"Unknown protocol %s://\", _addr.protocol);\n+\t}\n+\n+\tif (!strcmp(_addr.protocol, \"git\") && _addr.user) {\n+\t\tdie(\"git:// does not support users\");\n+\t}\n+\n+\tif (_addr.port) {\n+\t\tchar *end;\n+\t\tunsigned long x;\n+\t\tx = strtoul(_addr.port, &end, 10);\n+\t\tif (*end)\n+\t\t\tgoto bad;\n+\t\tif (x < 1 || x > 65535)\n+\t\t\tgoto bad;\n+\t\t_addr._port = (unsigned short)x;\n+\t} else if (!strcmp(_addr.protocol, \"gits\")) {\n+\t\t_addr._port = 9418;\n+\t} else if (!strcmp(_addr.protocol, \"git\")) {\n+\t\t_addr._port = 9418;\n+\t} else if (!strcmp(_addr.protocol, \"tls\")) {\n+\t\tdie(\"tls:// needs port specification\");\n+\t}\n+\n+\tif (_addr.port) {\n+\t\t/* 9 is for host=[]:\\0 */\n+\t\tsize_t vhost_len = 7 + strlen(_addr.host) +\n+\t\t\tstrlen(_addr.port);\n+\t\t_addr.vhost_header = xmalloc(vhost_len);\n+\t\t_addr.uhost = xmalloc(vhost_len);\n+\t} else {\n+\t\t/* 8 is for host=[]\\0 */\n+\t\tsize_t vhost_len = 6 + strlen(_addr.host);\n+\t\t_addr.vhost_header = xmalloc(vhost_len);\n+\t\t_addr.uhost = xmalloc(vhost_len);\n+\t}\n+\n+\tstrcpy(_addr.vhost_header, \"host=\");\n+\tappend_uniq_address(_addr.vhost_header, &_addr);\n+\n+\tstrcpy(_addr.uhost, \"\");\n+\tappend_uniq_address(_addr.uhost, &_addr);\n+\n+\treturn _addr;\n+bad:\n+\tdie(\"Bad URL \\\"%s\\\"\", orig_addr);\n+\t/* Can't come here. */\n+\treturn _addr;\n+}\n+\n+#define MODE_ALLOW_EOF 0\n+#define MODE_HANDSHAKE 1\n+\n+static void traffic_loop(struct user *user, int mode)\n+{\n+\tfd_set rfds;\n+\tfd_set wfds;\n+\tint failcode = 0;\n+\tstruct timeval deadline;\n+\tint bound = 0;\n+\tint r;\n+\tFD_ZERO(&rfds);\n+\tFD_ZERO(&wfds);\n+\tuser_add_to_sets(user, &bound, &rfds, &wfds, &deadline);\n+\tif (bound == 0) {\n+\t\tfailcode = user_get_failure(user);\n+\t\tif (failcode)\n+\t\t\tgoto failed;\n+\t\treturn;\n+\t}\n+\tr = select(bound, &rfds, &wfds, NULL, NULL);\n+\tif (r < 0 && errno != EINTR) {\n+\t\tdie_errno(\"select() failed\");\n+\t} else if (r < 0) {\n+\t\tFD_ZERO(&rfds);\n+\t\tFD_ZERO(&wfds);\n+\t}\n+\tuser_service(user, &rfds, &wfds);\n+\tfailcode = user_get_failure(user);\n+\tif (failcode)\n+\t\tgoto failed;\n+\treturn;\n+failed:\n+\tif (failcode > 0 && mode == MODE_ALLOW_EOF)\n+\t\treturn;\n+\telse if (failcode > 0) {\n+\t\tdie(\"Expected more data, got connection closed\");\n+\t} else {\n+\t\tconst char *major;\n+\t\tconst char *minor;\n+\n+\t\tmajor = user_explain_failure(failcode);\n+\t\tminor = user_get_error(user);\n+\n+\t\tif (minor)\n+\t\t\tdie(\"Connection lost: %s (%s)\", major, minor);\n+\t\telse\n+\t\t\tdie(\"Connection lost: %s\", major);\n+\t}\n+}\n+\n+static int select_keypair(gnutls_certificate_credentials_t creds,\n+\tconst char *username, int must_succeed)\n+{\n+\tint ret;\n+\tret = select_keypair_int(creds, username);\n+\tif (ret < 0 && must_succeed)\n+\t\tdie(\"No keypair identity %s found\", username);\n+\treturn ret;\n+}\n+\n+static gnutls_session_t session;\n+\n+static void preconfigure_tls(const char *username)\n+{\n+\tint s;\n+\tgnutls_certificate_credentials_t creds;\n+\tint keypair_ok = 0;\n+#ifndef DISABLE_SRP\n+\tconst char *srp_password;\n+\tgnutls_srp_client_credentials_t srp_cred;\n+\tint kx[3];\n+#endif\n+\n+\ts = gnutls_global_init();\n+\tif (s < 0)\n+\t\tdie(\"Can't initialize GnuTLS: %s\", gnutls_strerror(s));\n+\n+\ts = gnutls_certificate_allocate_credentials(&creds);\n+\tif (s < 0)\n+\t\tdie(\"Can't allocate cert creds: %s\", gnutls_strerror(s));\n+\n+\ts = gnutls_init(&session, GNUTLS_CLIENT);\n+\tif (s < 0)\n+\t\tdie(\"Can't allocate session: %s\", gnutls_strerror(s));\n+\n+#ifndef DISABLE_SRP\n+\ts = gnutls_priority_set_direct (session, \"NORMAL:+SRP-DSS:+SRP-RSA\",\n+\t\tNULL);\n+#else\n+\ts = gnutls_priority_set_direct (session, \"NORMAL\", NULL);\n+#endif\n+\tif (s < 0)\n+\t\tdie(\"Can't set priority: %s\", gnutls_strerror(s));\n+\n+\tif (username) {\n+\t\tif (!prefixcmp(username, \"key-\")) {\n+\t\t\tselect_keypair(creds, username + 4, 1);\n+\t\t\tkeypair_ok = 1;\n+\t\t} else\n+\t\t\tkeypair_ok = (select_keypair(creds, username, 0)\n+\t\t\t\t>= 0);\n+\t}\n+\n+\ts = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, creds);\n+\tif (s < 0)\n+\t\tdie(\"Can't set creds: %s\", gnutls_strerror(s));\n+\n+\tif (keypair_ok)\n+\t\tgoto no_srp;\n+#ifndef DISABLE_SRP\n+\tif (username && !prefixcmp(username, \"srp-\"))\n+\t\tusername = username + 4;\n+\tif (!username || !*username)\n+\t\tgoto no_srp;\n+\n+\ts = gnutls_srp_allocate_client_credentials(&srp_cred);\n+\tif (s < 0)\n+\t\tdie(\"Can't allocate SRP creds: %s\", gnutls_strerror(s));\n+\n+\ts = 0;\n+\tsrp_password = get_srp_password(username);\n+\ts = gnutls_srp_set_client_credentials(srp_cred, username, srp_password);\n+\tif (s < 0)\n+\t\tdie(\"Can't set SRP creds: %s\", gnutls_strerror(s));\n+\n+\ts = gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred);\n+\tif (s < 0)\n+\t\tdie(\"Can't use SRP creds: %s\", gnutls_strerror(s));\n+\n+\t/* GnuTLS doesn't seem to like to use SRP. Force it. */\n+\tkx[0] = GNUTLS_KX_SRP_DSS;\n+\tkx[1] = GNUTLS_KX_SRP_RSA;\n+\tkx[2] = 0;\n+\ts = gnutls_kx_set_priority(session, kx);\n+\tif (s < 0)\n+\t\tdie(\"Can't force SRP: %s\", gnutls_strerror(s));\n+#endif\n+no_srp:\n+\t;\n+}\n+\n+static void configure_tls(struct user *user, const char *hostname)\n+{\n+\tuser_configure_tls(user, session);\n+\n+\t/* Wait for TLS connection to establish. */\n+\twhile (!user_get_tls(user))\n+\t\ttraffic_loop(user, MODE_HANDSHAKE);\n+\n+\tcheck_hostkey(session, hostname);\n+}\n+\n+#define MAX_REQUEST 8192\n+const char *hexes = \"0123456789abcdef\";\n+\n+static void do_request(const char *arg, struct parsed_addr *addr,\n+\tint supress_ok)\n+{\n+\tint fd;\n+\tstruct user *dispatcher;\n+\tstruct cbuffer *inbuf;\n+\tstruct cbuffer *outbuf;\n+\tconst char *major;\n+\tconst char *minor;\n+\tchar reqbuf[MAX_REQUEST + 4];\n+\tsize_t reqsize;\n+\n+\tpreconfigure_tls(addr->user);\n+\n+\tfd = connect_host(addr->host, addr->_port);\n+\n+\t/* Create dispatcher with no time limit. */\n+\tdispatcher = user_create(fd, 65535);\n+\tif (!dispatcher)\n+\t\tdie(\"Can't create connection context\");\n+\tuser_clear_deadline(dispatcher);\n+\n+\tinbuf = user_get_red_in(dispatcher);\n+\toutbuf = user_get_red_out(dispatcher);\n+\tif (!strcmp(addr->protocol, \"git\")) {\n+\t\t; /* Not protected. */\n+\t} else if (!strcmp(addr->protocol, \"tls\")) {\n+\t\tconfigure_tls(dispatcher, addr->uhost);\n+\t} else {\n+\t\tcbuffer_write(inbuf, (unsigned char*)\"000cstarttls\", 12);\n+\t\twhile (1) {\n+\t\t\tchar tmpbuf[9];\n+\t\t\tint s;\n+\t\t\ttraffic_loop(dispatcher, MODE_HANDSHAKE);\n+\t\t\ts = cbuffer_peek(outbuf, (unsigned char*)tmpbuf, 8);\n+\t\t\ttmpbuf[8] = '\\0';\n+\t\t\tif (s >= 0 && !strcmp(tmpbuf, \"proceed\\n\"))\n+\t\t\t\tbreak;\n+\t\t\tif (user_red_out_eofd(dispatcher))\n+\t\t\t\tgoto wait_eofd;\n+\t\t\tif (user_get_failure(dispatcher))\n+\t\t\t\tgoto wait_failed;\n+\t\t}\n+\t\tconfigure_tls(dispatcher, addr->uhost);\n+\t}\n+\n+\treqsize = strlen(arg) + strlen(addr->path) +  3 +\n+\t\tstrlen(addr->vhost_header);\n+\n+\tif (reqsize > MAX_REQUEST)\n+\t\tdie(\"Request too big to send\");\n+\n+\tmemcpy(reqbuf + 4, arg, strlen(arg));\n+\treqbuf[strlen(arg) + 4] = ' ';\n+\tmemcpy(reqbuf + strlen(arg) + 5, addr->path, strlen(addr->path) + 1);\n+\tmemcpy(reqbuf + strlen(arg) + 6 + strlen(addr->path),\n+\t\taddr->vhost_header, strlen(addr->vhost_header) + 1);\n+\n+\treqbuf[0] = hexes[((reqsize + 4) >> 12) & 0xF];\n+\treqbuf[1] = hexes[((reqsize + 4) >> 8) & 0xF];\n+\treqbuf[2] = hexes[((reqsize + 4) >> 4) & 0xF];\n+\treqbuf[3] = hexes[(reqsize + 4) & 0xF];\n+\n+\tcbuffer_write(inbuf, (unsigned char*)reqbuf, reqsize + 4);\n+\twhile (cbuffer_used(outbuf))\n+\t\ttraffic_loop(dispatcher, MODE_HANDSHAKE);\n+\t/* Ok, remote end has replied. */\n+\tprintf(\"\\n\");\n+\tfflush(stdout);\n+\tuser_set_red_io(dispatcher, 0, 1, -1);\n+\n+\twhile (!user_get_failure(dispatcher))\n+\t\ttraffic_loop(dispatcher, MODE_ALLOW_EOF);\n+\texit(0);\n+\n+wait_failed:\n+\tmajor = user_explain_failure(user_get_failure(dispatcher));\n+\tminor = user_get_error(dispatcher);\n+\n+\tif (minor)\n+\t\tdie(\"Connection lost: %s (%s)\", major, minor);\n+\telse\n+\t\tdie(\"Connection lost: %s\", major);\n+\texit(128);\n+wait_eofd:\n+\tdie(\"Expected response to starttls, server closed connection.\");\n+\texit(128);\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tstruct parsed_addr paddr;\n+\tchar buffer[8192];\n+\n+\tif (argc < 3) {\n+\t\tdie(\"Need two arguments\");\n+\t}\n+\n+\tpaddr = parse_address(argv[2]);\n+\n+\tif (!prefixcmp(argv[1], \"--service=\")) {\n+\t\tdo_request(argv[1] + 10, &paddr, 1);\n+\t\treturn 0;\n+\t}\n+\n+\twhile (1) {\n+\t\tchar *cmd;\n+\n+\t\tcmd = fgets(buffer, 8190, stdin);\n+\t\tif (cmd[strlen(cmd) - 1] == '\\n')\n+\t\t\tcmd[strlen(cmd) - 1] = '\\0';\n+\n+\t\tif (!strcmp(cmd, \"capabilities\")) {\n+\t\t\tprintf(\"*connect\\n\\n\");\n+\t\t\tfflush(stdout);\n+\t\t} else if (!*cmd) {\n+\t\t\texit(0);\n+\t\t} else if (!prefixcmp(cmd, \"connect \")) {\n+\t\t\tdo_request(cmd + 8, &paddr, 0);\n+\t\t\treturn 0;\n+\t\t} else\n+\t\t\tdie(\"Unknown command %s\", cmd);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/git-over-tls/home.h b/git-over-tls/misc.c\nsimilarity index 64%\ncopy from git-over-tls/home.h\ncopy to git-over-tls/misc.c\nindex 133ee78..3d3e0b3 100644\n--- a/git-over-tls/home.h\n+++ b/git-over-tls/misc.c\n@@ -5,9 +5,11 @@\n  * it under the terms of the GNU General Public License version 2 as\n  * published by the Free Software Foundation.\n  */\n-#ifndef _home__h__included__\n-#define _home__h__included__\n+#include \"misc.h\"\n+#include <unistd.h>\n+#include <errno.h>\n \n-const char *get_home();\n-\n-#endif\n+void force_close(int fd)\n+{\n+\twhile (close(fd) < 0 && errno != EBADF);\n+}\ndiff --git a/git-over-tls/keypairs.h b/git-over-tls/misc.h\nsimilarity index 50%\ncopy from git-over-tls/keypairs.h\ncopy to git-over-tls/misc.h\nindex 11f4ef7..140341f 100644\n--- a/git-over-tls/keypairs.h\n+++ b/git-over-tls/misc.h\n@@ -5,12 +5,23 @@\n  * it under the terms of the GNU General Public License version 2 as\n  * published by the Free Software Foundation.\n  */\n-#ifndef _keypairs__h__included__\n-#define _keypairs__h__included__\n+#ifndef _misc__h__included__\n+#define _misc__h__included__\n \n-#include <gnutls/openpgp.h>\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+/*\n+ * Forcibly close the file descriptor.\n+ *\n+ * Input\n+ *\tfd\t\tThe file descriptor.\n+ */\n+void force_close(int fd);\n \n-int select_keypair_int(gnutls_certificate_credentials_t creds,\n-\tconst char *username);\n+#ifdef __cplusplus\n+}\n+#endif\n \n #endif\ndiff --git a/git-over-tls/mkcert.c b/git-over-tls/mkcert.c\nnew file mode 100644\nindex 0000000..597f942\n--- /dev/null\n+++ b/git-over-tls/mkcert.c\n@@ -0,0 +1,507 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009-2010\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"cbuffer.h\"\n+#include \"home.h\"\n+#include \"prompt.h\"\n+#include <unistd.h>\n+#include <stdlib.h>\n+#include <stdio.h>\n+#include <fcntl.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <signal.h>\n+#include <sys/stat.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#include \"run-command.h\"\n+#endif\n+\n+#define CERT_MAX 65536\n+#define BUFSIZE 8192\n+\n+void ensure_leading_directories()\n+{\n+\tstruct stat s;\n+\tchar fsobj[BUFSIZE];\n+\tint r;\n+\n+\tsprintf(fsobj, \"%s/.gits\", get_home());\n+\tr = stat(fsobj, &s);\n+\tif (r < 0 && errno != ENOENT)\n+\t\tdie_errno(\"Stat $HOME/.gits\");\n+\telse if (r == 0 && !S_ISDIR(s.st_mode))\n+\t\tdie(\"$HOME/.gits exists but is not a directory\");\n+\telse if (r < 0) {\n+\t\t/* Need to create it. */\n+\t\tif (mkdir(fsobj, 0700) < 0)\n+\t\t\tdie_errno(\"Create $HOME/.gits failed\");\n+\t}\n+\t/* Otherwise, r == 0 && S_ISDIR(s), which is OK. */\n+\tsprintf(fsobj, \"%s/.gits/keys\", get_home());\n+\tr = stat(fsobj, &s);\n+\tif (r < 0 && errno != ENOENT)\n+\t\tdie_errno(\"Stat $HOME/.gits/keys\");\n+\telse if (r == 0 && !S_ISDIR(s.st_mode))\n+\t\tdie(\"$HOME/.gits/keys exists but is not a directory\");\n+\telse if (r < 0) {\n+\t\t/* Need to create it. */\n+\t\tif (mkdir(fsobj, 0700) < 0)\n+\t\t\tdie_errno(\"Create $HOME/.gits/keys failed\");\n+\t }\n+}\n+\n+\n+static int seal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,\n+\tconst char *sealer)\n+{\n+\tstruct child_process child;\n+\tchar **argv;\n+\tchar *sealer_copy;\n+\tint splits = 0;\n+\tint escape = 0;\n+\tint ridx, widx, tidx;\n+\tint cleanup = 0;\n+\tconst char *i;\n+\n+\tsignal(SIGPIPE, SIG_IGN);\n+\n+\tfor (i = sealer; *i; i++) {\n+\t\tif (escape)\n+\t\t\tescape = 0;\n+\t\telse if (*i == '\\\\')\n+\t\t\tescape = 1;\n+\t\telse if (*i == ' ')\n+\t\t\tsplits++;\n+\t}\n+\n+\targv = xmalloc((splits + 2) * sizeof(char*));\n+\targv[splits + 1] = NULL;\n+\n+\tsealer_copy = xstrdup(sealer);\n+\targv[0] = sealer_copy;\n+\n+\tridx = 0;\n+\twidx = 0;\n+\ttidx = 1;\n+\tescape = 0;\n+\twhile (sealer_copy[ridx]) {\n+\t\tif (escape) {\n+\t\t\tescape = 0;\n+\t\t\tsealer_copy[widx++] = sealer_copy[ridx++];\n+\t\t} else if (sealer_copy[ridx] == '\\\\') {\n+\t\t\tridx++;\n+\t\t\tescape = 1;\n+\t\t} else if (sealer_copy[ridx] == ' ') {\n+\t\t\tsealer_copy[widx++] = '\\0';\n+\t\t\targv[tidx++] = sealer_copy + widx;\n+\t\t\tridx++;\n+\t\t} else\n+\t\t\tsealer_copy[widx++] = sealer_copy[ridx++];\n+\t}\n+\tsealer_copy[widx] = '\\0';\n+\n+\tmemset(&child, 0, sizeof(child));\n+\tchild.argv = (const char**)argv;\n+\tchild.in = -1;\n+\tchild.out = -1;\n+\tchild.err = 0;\n+\tif (start_command(&child))\n+\t\treturn -1;\n+\tcleanup = 1;\n+\n+\twhile (1) {\n+\t\tint bound;\n+\t\tfd_set rf;\n+\t\tfd_set wf;\n+\t\tint r;\n+\n+\t\tFD_ZERO(&rf);\n+\t\tFD_ZERO(&wf);\n+\t\tFD_SET(child.out, &rf);\n+\t\tif (cbuffer_used(unsealed))\n+\t\t\tFD_SET(child.in, &wf);\n+\t\telse\n+\t\t\tclose(child.in);\n+\n+\t\tif (cbuffer_used(sealed))\n+\t\t\tbound = ((child.out > child.in) ? child.out :\n+\t\t\t\tchild.in) + 1;\n+\t\telse\n+\t\t\tbound = child.out + 1;\n+\n+\t\tr = select(bound, &rf, &wf, NULL, NULL);\n+\t\tif (r < 0 && r != EINTR) {\n+\t\t\tperror(\"Select\");\n+\t\t\tgoto exit_error;\n+\t\t}\n+\t\tif (r < 0) {\n+\t\t\tFD_ZERO(&rf);\n+\t\t\tFD_ZERO(&wf);\n+\t\t\tperror(\"select\");\n+\t\t}\n+\n+\t\tif (FD_ISSET(child.out, &rf)) {\n+\t\t\tr = cbuffer_read_fd(sealed, child.out);\n+\t\t\tif (r < 0 && errno != EINTR && errno != EAGAIN) {\n+\t\t\t\tfprintf(stderr, \"Read from sealer \"\n+\t\t\t\t\t\"failed: %s\", strerror(errno));\n+\t\t\t\tgoto exit_error;\n+\t\t\t}\n+\t\t\tif (r < 0 && errno == EAGAIN)\n+\t\t\t\tif (!cbuffer_free(sealed)) {\n+\t\t\t\t\tfprintf(stderr, \"Keypair too big\\n\");\n+\t\t\t\t\tgoto exit_error;\n+\t\t\t}\n+\t\t\tif (r < 0)\n+\t\t\t\tperror(\"read\");\n+\t\t\tif (r == 0)\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (FD_ISSET(child.in, &wf)) {\n+\t\t\tr = cbuffer_write_fd(unsealed, child.in);\n+\t\t\tif (r < 0 && errno == EPIPE) {\n+\t\t\t\tfprintf(stderr, \"Sealer exited \"\n+\t\t\t\t\t\"unexpectedly\\n\");\n+\t\t\t\tgoto exit_error;\n+\t\t\t}\n+\t\t\tif (r < 0 && errno != EINTR && errno != EAGAIN) {\n+\t\t\t\tfprintf(stderr, \"Write to sealer \"\n+\t\t\t\t\t\"failed: %s\", strerror(errno));\n+\t\t\t\tgoto exit_error;\n+\t\t\t}\n+\t\t\tif (r < 0)\n+\t\t\t\tperror(\"write\");\n+\t\t}\n+\t}\n+\n+\tif (finish_command(&child)) {\n+\t\tcleanup = 0;\n+\t\tgoto exit_error;\n+\t}\n+\n+\tclose(child.in);\n+\tclose(child.out);\n+\n+\treturn 0;\n+exit_error:\n+\tclose(child.in);\n+\tclose(child.out);\n+\treturn -1;\n+}\n+\n+static void append_member(struct cbuffer *cbuf, const char *filename)\n+{\n+\tunsigned char backing[CERT_MAX];\n+\tstruct cbuffer *content;\n+\tint fd;\n+\tsize_t size = 0;\n+\tunsigned char buf[2];\n+\n+\tcontent = cbuffer_create(backing, CERT_MAX);\n+\tfd = open(filename, O_RDONLY);\n+\tif (fd < 0) {\n+\t\tperror(\"open\");\n+\t\texit(1);\n+\t}\n+\twhile (1) {\n+\t\tssize_t r = cbuffer_read_fd(content, fd);\n+\t\tif (r < 0) {\n+\t\t\tif (errno == EAGAIN) {\n+\t\t\t\tif (!cbuffer_free(content)) {\n+\t\t\t\t\tfprintf(stderr, \"Member too big.\\n\");\n+\t\t\t\t\tunlink(\"key.private.tmp\");\n+\t\t\t\t\tunlink(\"key.public.tmp\");\n+\t\t\t\t\texit(1);\n+\t\t\t\t}\n+\t\t\t} else if (errno != EINTR) {\n+\t\t\t\tperror(\"read\");\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (r == 0) {\n+\t\t\tbreak;\n+\t\t} else\n+\t\t\tsize += r;\n+\t}\n+\tclose(fd);\n+\n+\tbuf[0] = (unsigned char)((size >> 8) & 0xFF);\n+\tbuf[1] = (unsigned char)((size) & 0xFF);\n+\tif (cbuffer_write(cbuf, buf, 2) < 0) {\n+\t\tfprintf(stderr, \"Certificate too big (can't write member header).\\n\");\n+\t\tunlink(\"key.private.tmp\");\n+\t\tunlink(\"key.public.tmp\");\n+\t\texit(1);\n+\t}\n+\tif (cbuffer_move(cbuf, content, size) < 0) {\n+\t\tfprintf(stderr, \"Certificate too big (can't write member of %u \"\n+\t\t\t\"bytes).\\n\", size);\n+\t\tunlink(\"key.private.tmp\");\n+\t\tunlink(\"key.public.tmp\");\n+\t\texit(1);\n+\t}\n+\n+\tcbuffer_destroy(content);\n+}\n+\n+\n+static char *escape(char *s)\n+{\n+\tchar *ans;\n+\tint ridx = 0, widx = 0;\n+\n+\tans = xmalloc(2 * strlen(s) + 1);\n+\twhile (s[ridx]) {\n+\t\tif (s[ridx] == '\\\\') {\n+\t\t\tans[widx++] = '\\\\';\n+\t\t\tans[widx++] = '\\\\';\n+\t\t\tridx++;\n+\t\t} else if (s[ridx] == ' ') {\n+\t\t\tans[widx++] = '\\\\';\n+\t\t\tans[widx++] = ' ';\n+\t\t\tridx++;\n+\t\t} else {\n+\t\t\tans[widx++] = s[ridx++];\n+\t\t}\n+\t}\n+\tans[widx] = '\\0';\n+\tfree(s);\n+\treturn ans;\n+}\n+\n+static int check_name(char* s)\n+{\n+\tsize_t x;\n+\tx = strspn(s, \" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n+\t\t\"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~\");\n+\tif (!x || s[x]) {\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int check_comment(char* s)\n+{\n+\tchar *at;\n+\tat = strchr(s, '(');\n+\tif (at)\n+\t\treturn -1;\n+\tat = strchr(s, '\\\\');\n+\tif (at)\n+\t\treturn -1;\n+\tat = strchr(s, ')');\n+\tif (at)\n+\t\treturn -1;\n+\tat = s;\n+\twhile (*at >= 32 && *at <= 126)\n+\t\tat++;\n+\tif (*at)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static int check_email(char* s)\n+{\n+\tsize_t x;\n+\tchar *at;\n+\tat = strchr(s, '@');\n+\tif (!at)\n+\t\treturn -1;\n+\tx = strspn(s, \".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n+\t\t\"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~\");\n+\tif (s[x] != '@')\n+\t\treturn -1;\n+\tif (!at[1])\n+\t\treturn -1;\n+\tx = strspn(at + 1, \".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n+\t\t\"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~\");\n+\tif (at[x + 1])\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+#define BUFSIZE 8192\n+\n+void write_cert(int server_mode)\n+{\n+\tunsigned char backing1[CERT_MAX];\n+\tunsigned char backing2[CERT_MAX];\n+\tunsigned char keytemp[CERT_MAX];\n+\tstruct cbuffer *unsealed;\n+\tstruct cbuffer *sealed;\n+\tunsigned char *buf = (unsigned char*)\"GITSUCERT\";\n+\tunsigned char *buf2 = (unsigned char*)\"GITSSCERT\\x00\\x03gpg\";\n+\tchar *name;\n+\tchar fbuffer[BUFSIZE];\n+\tint fd;\n+\tint do_seal = 0;\n+\tint keylen = 1024;\n+\tchar *realname;\n+\tchar *comment;\n+\tchar *email;\n+\tFILE *script;\n+\n+\tif (!server_mode)\n+\t\tensure_leading_directories();\n+\n+\tunsealed = cbuffer_create(backing1, CERT_MAX);\n+\tsealed = cbuffer_create(backing2, CERT_MAX);\n+\n+reask_length:\n+\tprintf(\"1) 1024 bit key\\n\");\n+\tprintf(\"2) 2048 bit key\\n\");\n+\tprintf(\"3) 3072 bit key\\n\");\n+\tname = prompt_string(\"Pick key length\", 0);\n+\tif (!strcmp(name, \"1\"))\n+\t\tkeylen = 1024;\n+\telse if (!strcmp(name, \"2\"))\n+\t\tkeylen = 2048;\n+\telse if (!strcmp(name, \"3\"))\n+\t\tkeylen = 3072;\n+\telse {\n+\t\tfprintf(stderr, \"Bad choice\\n\");\n+\t\tgoto reask_length;\n+\t}\n+\n+ask_name:\n+\trealname = prompt_string(\"Enter name to put into key\", 0);\n+\tif (check_name(realname) < 0) {\n+\t\tfprintf(stderr, \"Bad name\\n\");\n+\t\tgoto ask_name;\n+\t}\n+ask_comment:\n+\tcomment = prompt_string(\"Enter comment to put into key\", 0);\n+\tif (check_comment(comment) < 0) {\n+\t\tfprintf(stderr, \"Bad comment\\n\");\n+\t\tgoto ask_comment;\n+\t}\n+ask_email:\n+\temail = prompt_string(\"Enter E-mail address to put into key\", 0);\n+\tif (check_email(email) < 0) {\n+\t\tfprintf(stderr, \"Bad E-mail address\\n\");\n+\t\tgoto ask_email;\n+\t}\n+\n+\tscript = fopen(\"key.script.tmp\", \"w\");\n+\tif (!script)\n+\t\tdie(\"Can't create key script\");\n+\tfprintf(script, \"Key-Type: DSA\\n\");\n+\tfprintf(script, \"Key-Length: %i\\n\", keylen);\n+\tfprintf(script, \"Name-Real: %s\\n\", realname);\n+\tif (*comment)\n+\t\tfprintf(script, \"Name-Comment: %s\\n\", comment);\n+\tfprintf(script, \"Name-Email: %s\\n\", email);\n+\tfprintf(script, \"Expire-Date: 0\\n\");\n+\tfprintf(script, \"%%pubring key.public.tmp\\n\");\n+\tfprintf(script, \"%%secring key.private.tmp\\n\");\n+\tfprintf(script, \"%%commit\\n\");\n+\tfprintf(script, \"%%echo done\\n\");\n+\tfclose(script);\n+\n+\tif (system(\"gpg --batch --gen-key key.script.tmp\")) {\n+\t\tunlink(\"key.private.tmp\");\n+\t\tunlink(\"key.public.tmp\");\n+\t\tunlink(\"key.script.tmp\");\n+\t\tdie(\"Can't generate key\");\n+\t}\n+\tunlink(\"key.script.tmp\");\n+\n+\tappend_member(unsealed, \"key.private.tmp\");\n+\tunlink(\"key.private.tmp\");\n+\tappend_member(unsealed, \"key.public.tmp\");\n+\tunlink(\"key.public.tmp\");\n+\n+reask_seal:\n+\tif (server_mode)\n+\t\tgoto no_seal;\n+\tprintf(\"1) Don't seal key\\n\");\n+\tprintf(\"2) Seal using password (gpg)\\n\");\n+\tprintf(\"3) Seal using keypair (gpg)\\n\");\n+\tname = prompt_string(\"Pick sealing method\", 0);\n+\tif (!strcmp(name, \"1\"))\n+\t\tdo_seal = 0;\n+\telse if (!strcmp(name, \"2\"))\n+\t\tdo_seal = 1;\n+\telse if (!strcmp(name, \"3\"))\n+\t\tdo_seal = 2;\n+\telse {\n+\t\tfprintf(stderr, \"Bad choice\");\n+\t\tgoto reask_seal;\n+\t}\n+no_seal:\n+\tkeylen = cbuffer_read_max(unsealed, keytemp, CERT_MAX);\n+\tcbuffer_write(unsealed, keytemp, keylen);\n+\n+\tif (do_seal == 1) {\n+\t\tcbuffer_write(sealed, (unsigned char*)buf2, 14);\n+\t\tif (seal_cert(sealed, unsealed, \"gpg --symmetric \"\n+\t\t\t\"--force-mdc\") < 0) {\n+\t\t\tcbuffer_clear(sealed);\n+\t\t\tcbuffer_clear(unsealed);\n+\t\t\tcbuffer_write(unsealed, keytemp, keylen);\n+\t\t\tfprintf(stderr, \"Sealing failed.\\n\");\n+\t\t\tgoto reask_seal;\n+\t\t}\n+\t} else if (do_seal == 2) {\n+\t\tchar* hint;\n+\t\tcbuffer_write(sealed, (unsigned char*)buf2, 14);\n+\n+\t\thint = prompt_string(\"Seal using whose key\", 0);\n+\t\thint = escape(hint);\n+\t\tsprintf(fbuffer, \"gpg --encrypt --recipient %s \"\n+\t\t\t\"--force-mdc\", hint);\n+\t\tfree(hint);\n+\n+\t\tif (seal_cert(sealed, unsealed, fbuffer) < 0) {\n+\t\t\tcbuffer_clear(sealed);\n+\t\t\tcbuffer_clear(unsealed);\n+\t\t\tcbuffer_write(unsealed, keytemp, keylen);\n+\t\t\tfprintf(stderr, \"Sealing failed.\\n\");\n+\t\t\tgoto reask_seal;\n+\t\t}\n+\t} else {\n+\t\tcbuffer_write(sealed, (unsigned char*)buf, 9);\n+\t\tcbuffer_move_nolimit(sealed, unsealed);\n+\t\tif (!cbuffer_free(sealed))\n+\t\t\tdie(\"Key too large\");\n+\t}\n+\n+retry_name:\n+\tif (server_mode) {\n+\t\tname = prompt_string(\"Enter filename to save key as\", 0);\n+\t\tstrcpy(fbuffer, name);\n+\t} else {\n+\t\tname = prompt_string(\"Enter name for key\", 0);\n+\t\tif (strcspn(name, \"@:/[\") < strlen(name)) {\n+\t\t\tfprintf(stderr, \"Bad name\\n\");\n+\t\t\tgoto retry_name;\n+\t\t}\n+\t\tsprintf(fbuffer, \"%s/.gits/keys/%s\", get_home(), name);\n+\t}\n+\tif (!strcmp(name, \"\")) {\n+\t\tfprintf(stderr, \"Bad name\\n\");\n+\t\tgoto retry_name;\n+\t}\n+\n+\tfd = open(fbuffer, O_WRONLY | O_CREAT | O_EXCL, 0600);\n+\tif (fd < 0) {\n+\t\tfprintf(stderr, \"Can't open %s: %s\\n\", fbuffer,\n+\t\t\tstrerror(errno));\n+\t\tgoto retry_name;\n+\t}\n+\twhile (cbuffer_used(sealed)) {\n+\t\tssize_t r = cbuffer_write_fd(sealed, fd);\n+\t\tif (r < 0 && errno != EINTR) {\n+\t\t\tperror(\"write\");\n+\t\t\texit(1);\n+\t\t}\n+\t}\n+\tclose(fd);\n+}\ndiff --git a/git-over-tls/prompt.c b/git-over-tls/prompt.c\nnew file mode 100644\nindex 0000000..380156a\n--- /dev/null\n+++ b/git-over-tls/prompt.c\n@@ -0,0 +1,100 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"prompt.h\"\n+#include <termios.h>\n+#include <signal.h>\n+#include <unistd.h>\n+#include <stdio.h>\n+#include <fcntl.h>\n+#include <string.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+static int tmpfd;\n+\n+static void sigint(int x);\n+\n+void echo_off(int fd)\n+{\n+\tstruct termios t;\n+\n+\tif (tcgetattr(fd, &t) < 0)\n+\t\tdie_errno(\"Can't read terminal settings\");\n+\n+\tt.c_lflag &= ~ECHO;\n+\n+\ttmpfd = fd;\n+\tsignal(SIGINT, sigint);\n+\n+\tif (tcsetattr(fd, TCSANOW, &t) < 0)\n+\t\tdie_errno(\"Can't write terminal settings\");\n+}\n+\n+void echo_on(int fd)\n+{\n+\tstruct termios t;\n+\n+\tif (tcgetattr(fd, &t) < 0)\n+\t\tdie_errno(\"Can't read terminal settings\");\n+\n+\tt.c_lflag |= ECHO;\n+\n+\tif (tcsetattr(fd, TCSANOW, &t) < 0)\n+\t\tdie_errno(\"Can't write terminal settings\");\n+\n+\tsignal(SIGINT, SIG_DFL);\n+}\n+\n+static void sigint(int x)\n+{\n+\techo_on(tmpfd);\n+\texit(1);\n+}\n+\n+#define PROMPTBUF 8192\n+\n+char *prompt_string(const char *prompt, int without_echo)\n+{\n+\tchar ansbuf[8192];\n+\tchar *ans;\n+\tint fd;\n+\tFILE* tty;\n+\n+\tfd = open(\"/dev/tty\", O_RDWR);\n+\tif (fd < 0)\n+\t\tdie_errno(\"Can't open /dev/tty for password prompt\");\n+\n+\ttty = xfdopen(fd, \"r+\");\n+\n+\tfprintf(tty, \"%s: \", prompt);\n+\tfflush(tty);\n+\tif (without_echo)\n+\t\techo_off(fd);\n+\n+\tif (!fgets(ansbuf, 8190, tty)) {\n+\t\tif (without_echo)\n+\t\t\techo_on(fd);\n+\t\tdie(\"Can't read answer\");\n+\t}\n+\n+\tif (without_echo) {\n+\t\tfprintf(tty, \"\\n\");\n+\t\techo_on(fd);\n+\t}\n+\n+\tif (*ansbuf && ansbuf[strlen(ansbuf) - 1] == '\\n')\n+\t\tansbuf[strlen(ansbuf) - 1] = '\\0';\n+\n+\tfclose(tty);\n+\n+\tans = xstrdup(ansbuf);\n+\treturn ans;\n+}\ndiff --git a/git-over-tls/prompt.h b/git-over-tls/prompt.h\nnew file mode 100644\nindex 0000000..34fc13a\n--- /dev/null\n+++ b/git-over-tls/prompt.h\n@@ -0,0 +1,18 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _prompt__h__included__\n+#define _prompt__h__included__\n+\n+/* Turn terminal echo on specified fd off. */\n+void echo_off(int fd);\n+/* Turn terminal echo on specified fd on. */\n+void echo_on(int fd);\n+/* Prompt string from user and return mallocced copy of it. */\n+char *prompt_string(const char *prompt, int without_echo);\n+\n+#endif\ndiff --git a/git-over-tls/srp_askpass.c b/git-over-tls/srp_askpass.c\nnew file mode 100644\nindex 0000000..0c0da36\n--- /dev/null\n+++ b/git-over-tls/srp_askpass.c\n@@ -0,0 +1,90 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"srp_askpass.h\"\n+#include \"prompt.h\"\n+#include <fcntl.h>\n+#include <unistd.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <stdio.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+#define PROMPTBUF 8192\n+#define CMDBUFSIZE 16384\n+\n+/* Use GITS_ASKPASS to ask for password. */\n+static char *get_password_via_external(const char *username,\n+\tconst char *prog)\n+{\n+\tstatic char buffer[PROMPTBUF + 1];\n+\tstatic char cmdbuffer[CMDBUFSIZE + 1];\n+\tchar *ans;\n+\tint escape = 0;\n+\tint idx;\n+\tint len;\n+\tint widx = 0;\n+\tFILE *out;\n+\n+\tif (strchr(username, '\\\"'))\n+\t\tdie(\"Can't prompt for usernames containing '\\\"'\");\n+\n+\tlen = snprintf(buffer, PROMPTBUF + 1, \"\\\"Enter SRP password for %s\\\"\",\n+\t\tusername);\n+\tif (len < 0 || len > PROMPTBUF)\n+\t\tdie(\"SRP Username is insanely long\");\n+\n+\tfor (idx = 0; prog[idx]; idx++) {\n+\t\tif (!escape && prog[idx] == '%')\n+\t\t\tescape = 1;\n+\t\telse if (escape && prog[idx] == 'p') {\n+\t\t\tif (widx + strlen(buffer) >= CMDBUFSIZE)\n+\t\t\t\tdie(\"Command line too long\");\n+\t\t\tstrcpy(cmdbuffer + widx, buffer);\n+\t\t\twidx += strlen(buffer);\n+\t\t} else {\n+\t\t\tif (widx + 1 >= CMDBUFSIZE)\n+\t\t\t\tdie(\"Command line too long\");\n+\t\t\tcmdbuffer[widx++] = prog[idx];\n+\t\t\tescape = 0;\n+\t\t}\n+\t}\n+\tcmdbuffer[widx++] = '\\0';\n+\n+\tout = popen(cmdbuffer, \"r\");\n+\tif (!out)\n+\t\tdie_errno(\"Can't invoke $GITS_ASKPASS\");\n+\n+\tif (!fgets(buffer, PROMPTBUF - 2, out)) {\n+\t\tdie(\"Can't read password\");\n+\t}\n+\n+\tif (strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\\n')\n+\t\tbuffer[strlen(buffer) - 1] = '\\0';\n+\n+\tif (pclose(out))\n+\t\tdie(\"Authentication canceled\");\n+\n+\tans = xstrdup(buffer);\n+\treturn ans;\n+}\n+\n+char *get_srp_password(const char *username)\n+{\n+\tstatic char buffer[PROMPTBUF + 1];\n+\n+\tif (getenv(\"GITS_ASKPASS\"))\n+\t\treturn get_password_via_external(username,\n+\t\t\tgetenv(\"GITS_ASKPASS\"));\n+\n+\tsprintf(buffer, \"Enter SRP password for %s\", username);\n+\treturn prompt_string(buffer, 1);\n+}\ndiff --git a/git-over-tls/hostkey.h b/git-over-tls/srp_askpass.h\nsimilarity index 59%\ncopy from git-over-tls/hostkey.h\ncopy to git-over-tls/srp_askpass.h\nindex c0e7dfe..d7271fd 100644\n--- a/git-over-tls/hostkey.h\n+++ b/git-over-tls/srp_askpass.h\n@@ -5,11 +5,10 @@\n  * it under the terms of the GNU General Public License version 2 as\n  * published by the Free Software Foundation.\n  */\n-#ifndef _hostkey__h__included__\n-#define _hostkey__h__included__\n+#ifndef _srp_askpass__h__included__\n+#define _srp_askpass__h__included__\n \n-#include <gnutls/gnutls.h>\n-\n-void check_hostkey(gnutls_session_t session, const char *hostname);\n+/* Get SRP password. Return is malloced */\n+char *get_srp_password(const char *username);\n \n #endif\ndiff --git a/git-over-tls/user.c b/git-over-tls/user.c\nnew file mode 100644\nindex 0000000..2bb23f5\n--- /dev/null\n+++ b/git-over-tls/user.c\n@@ -0,0 +1,1384 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#include \"user.h\"\n+#include \"cbuffer.h\"\n+#include \"misc.h\"\n+#include <sys/select.h>\n+#include <sys/time.h>\n+#include <gnutls/gnutls.h>\n+#include <errno.h>\n+#include <stdarg.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <sys/socket.h>\n+#ifdef USE_TRAP_PAGING\n+#include <sys/mman.h>\n+#endif\n+\n+/*\n+ * NOTE: This code may not crash, call exit or anything similar unless\n+ * program state is corrupt or call parameters are completely invalid.\n+ * It also may not call Git APIs.\n+ */\n+\n+/* Main buffer size. */\n+#define BUFFERSIZE 65536\n+/*\n+ * Error buffer can be maximum of 65535-8 bytes and must be smaller or\n+ * equal in size to main buffers.\n+ */\n+#define ERR_BUFFERSIZE (65535-8)\n+\n+struct user\n+{\n+\t/* If 1, EOF received from black in at transport level. */\n+\tunsigned u_black_in_eof : 1,\n+\t/* If 1, EOF sent to black out at transport level. */\n+\t\tu_black_out_eof : 1,\n+\t/* If 1, EOF received from black in at TLS level. */\n+\t\tu_black_in_d_eof : 1,\n+\t/* If 1, EOF sent to black out at TLS level. */\n+\t\tu_black_out_d_eof : 1,\n+\t/* If 1, Assume that there is more input to come from red in. */\n+\t\tu_red_assume_more : 1,\n+\t/* If 1, TLS is active and ready to transfer data. */\n+\t\tu_tls_active : 1,\n+\t/* If 1, there has been data received from red in. */\n+\t\tu_red_in_have_data : 1,\n+\t/*\n+\t * Result of last read of decrypted data.\n+\t * 0 => Read was successful or not attempted yet.\n+\t * 1 => Read was blocked by insufficient input data.\n+\t * 2 => Read was blocked by insufficient output space.\n+\t * 3 => (Reserved)\n+\t */\n+\t\tu_want_read : 2,\n+\t/*\n+\t * Result of last write of decrypted data.\n+\t * 0 => Write was successful or not attempted yet.\n+\t * 1 => Write was blocked by insufficient input data.\n+\t * 2 => Write was blocked by insufficient output space.\n+\t * 3 => (Reserved)\n+\t */\n+\t\tu_want_write : 2,\n+\t/*\n+\t * Result of last handshake attempt.\n+\t * 0 => Handshake was successful or not attempted yet.\n+\t * 1 => Handshake was blocked by insufficient input data.\n+\t * 2 => Handshake was blocked by insufficient output space.\n+\t * 3 => (Reserved)\n+\t */\n+\t\tu_want_hand : 2,\n+\t/* Number of bytes of error header sent (0-8). */\n+\t\tu_red_err_hdr_sent : 4,\n+\t/* Delay TLS failure present. 1 for handshake, 2 otherwise */\n+\t\tu_delay_tls_failure : 2,\n+\t/* Has seen any data received. */\n+\t\tu_seen_input_data : 1;\n+\n+\t/* File descriptors. -1 if none. */\n+\tint u_black_fd;\t\t/* Input/Output */\n+\tint u_red_in_fd;\t/* Input */\n+\tint u_red_out_fd;\t/* Output */\n+\tint u_red_err_fd;\t/* Input */\n+\t/* The backing buffer for all transfer buffers and its size. */\n+\tunsigned char *u_buf_backing;\n+\tsize_t u_buf_backing_size;\n+\t/* The actual transfer buffers. */\n+\tstruct cbuffer *u_black_in_buf;\n+\tstruct cbuffer *u_black_out_buf;\n+\tstruct cbuffer *u_red_in_buf;\n+\tstruct cbuffer *u_red_out_buf;\n+\tstruct cbuffer *u_red_err_buf;\n+\t/* Deadline. Tv_sec is -1 if there is no deadline. */\n+\tstruct timeval u_deadline;\n+\t/*\n+\t * Why the connection was torn down. Delay_failure is delayed\n+\t * failure that becomes real after output buffer is flushed\n+\t * so that TLS alerts are sent. Also stored is delayed TLS alert\n+\t * that couldn't be sent yet.\n+\t */\n+\tint u_failure;\n+\tchar *u_errmsg;\n+\tint u_delay_failure;\n+\tgnutls_alert_description_t u_delay_alert;\n+\t/* Active TLS session. NULL if no TLS. */\n+\tgnutls_session_t u_tls_session;\n+};\n+\n+/* Initiate delayed failure on user. */\n+static void delay_cleanup_user(struct user *user, int failure,\n+\tconst char *error)\n+{\n+\t/* Store the cause of termination. */\n+\tif (!user->u_errmsg) {\n+\t\tif (error)\n+\t\t\tuser->u_errmsg = strdup(error);\n+\t\telse\n+\t\t\tuser->u_errmsg = NULL;\n+\t}\n+\tuser->u_delay_failure = failure;\n+}\n+\n+/* Clean up user connection immediately. */\n+static void cleanup_user(struct user *user, int failure, const char *error)\n+{\n+\t/* If there is TLS session, deallocate its resources. */\n+\tif (user->u_tls_session) {\n+\t\tgnutls_deinit(user->u_tls_session);\n+\t\tuser->u_tls_session = NULL;\n+\t}\n+\t/* Shutdown and close black input/output. */\n+\tif (user->u_black_fd >= 0) {\n+\t\tshutdown(user->u_black_fd, SHUT_WR);\n+\t\tforce_close(user->u_black_fd);\n+\t}\n+\tuser->u_black_fd = -1;\n+\n+\t/* Close red inputs/outputs. */\n+\tif (user->u_red_in_fd >= 0)\n+\t\tforce_close(user->u_red_in_fd);\n+\tuser->u_red_in_fd = -1;\n+\tif (user->u_red_out_fd >= 0)\n+\t\tforce_close(user->u_red_out_fd);\n+\tuser->u_red_out_fd = -1;\n+\tif (user->u_red_err_fd >= 0)\n+\t\tforce_close(user->u_red_err_fd);\n+\tuser->u_red_err_fd = -1;\n+\n+\t/* Store the cause of termination. */\n+\tif (!user->u_errmsg) {\n+\t\tif (error)\n+\t\t\tuser->u_errmsg = strdup(error);\n+\t\telse\n+\t\t\tuser->u_errmsg = NULL;\n+\t}\n+\tuser->u_failure = failure;\n+}\n+\n+/*\n+ * Process delay failure. If there is delayed failure and output buffer is\n+ * empty, make it real failure and disconnect user immediately.\n+ */\n+static int process_delay_failure(struct user *user)\n+{\n+\tif (user->u_failure)\n+\t\treturn 0;\n+\tif (!cbuffer_used(user->u_black_out_buf) && user->u_delay_failure) {\n+\t\tcleanup_user(user, user->u_delay_failure, NULL);\n+\t\treturn 0;\n+\t}\n+\treturn 0;\n+}\n+\n+/* Is this error from I/O syscall fatal? */\n+static int is_fatal_error(int error)\n+{\n+\treturn (error != EINTR && error != EAGAIN && error != EWOULDBLOCK);\n+}\n+\n+/* Is this error from GnuTLS I/O operation fatal? */\n+static int is_fatal_tls_error(int error)\n+{\n+\t/*\n+\t * GNUTLS_E_INTERRUPTED should never be seen. since custom push and\n+\t * pull functions can't return EINTR, only EAGAIN.\n+\t */\n+\treturn (error < 0 && error != GNUTLS_E_AGAIN);\n+}\n+\n+/* Handle fatal error received from GnuTLS functions. */\n+static int handle_tls_failure_code(struct user *user, int gnutls_code,\n+\tint handshaking)\n+{\n+\tint x;\n+\tchar error_buffer[8192];\n+\tif (gnutls_code == GNUTLS_E_FATAL_ALERT_RECEIVED) {\n+\t\t/* Fatal alert. Get the description and format message. */\n+\t\tgnutls_alert_description_t alert;\n+\t\talert = gnutls_alert_get(user->u_tls_session);\n+\t\tsprintf(error_buffer, \"TLS alert received: %s\",\n+\t\t\tgnutls_alert_get_name(alert));\n+\n+\t\tif (alert == GNUTLS_A_BAD_RECORD_MAC && handshaking)\n+\t\t\tsprintf(error_buffer, \"TLS alert received: %s \"\n+\t\t\t\t\"(incorrect password?)\",\n+\t\t\t\tgnutls_alert_get_name(alert));\n+\n+\t\t/* Terminate the connection. */\n+\t\tif (handshaking)\n+\t\t\tcleanup_user(user, USER_TLS_HAND_ERROR,\n+\t\t\t\terror_buffer);\n+\t\telse\n+\t\t\tcleanup_user(user, USER_TLS_ERROR,\n+\t\t\t\terror_buffer);\n+\t\treturn 0;\n+\t}\n+\t/*\n+\t * Alerts use handshake readyness indicator, so try to set it\n+\t * to \"perform I/O immediately\".\n+\t */\n+\tuser->u_want_hand = 0;\n+\t/*\n+\t * Mark that delay TLS failure is present and get the alert\n+\t * that should be sent. Also set error message.\n+\t */\n+\tuser->u_delay_tls_failure = 1;\n+#ifndef DISABLE_SRP\n+\t/* GnuTLS doesn't seem to have proper code for this. */\n+\tif (gnutls_code == GNUTLS_E_SRP_PWD_ERROR)\n+\t\tuser->u_delay_alert = GNUTLS_A_UNKNOWN_PSK_IDENTITY;\n+\telse\n+#endif\n+\t\tuser->u_delay_alert = (gnutls_alert_description_t)\n+\t\t\tgnutls_error_to_alert((int)gnutls_code, &x);\n+\tuser->u_errmsg = strdup(gnutls_strerror_name((int)gnutls_code));\n+\treturn 1;\n+}\n+\n+/* Handle black input activity */\n+static int black_in_handler(struct user *user, fd_set *rfds)\n+{\n+\t/* Don't attempt to read if connection has failed. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* The file descrptor must be marked readable. */\n+\tif (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, rfds))\n+\t\treturn 0;\n+\t/* Don't attempt to read already EOF'd file descriptor. */\n+\tif (user->u_black_in_eof)\n+\t\treturn 0;\n+\n+\tssize_t r = cbuffer_read_fd(user->u_black_in_buf, user->u_black_fd);\n+\tFD_CLR(user->u_black_fd, rfds);\n+\tif (r < 0 && is_fatal_error(errno)) {\n+\t\t/* Inbound connection faulted! */\n+\t\tcleanup_user(user, USER_LAYER4_ERROR, strerror(errno));\n+\t} else if (r == 0) {\n+\t\t/* Received EOF. */\n+\t\tuser->u_black_in_eof = 1;\n+\t} else if (r > 0) {\n+\t\t/* Received some data. */\n+\t\tuser->u_seen_input_data = 1;\n+\t}\n+\treturn 1;\n+}\n+\n+/* Handle black output activity. */\n+static int black_out_handler(struct user *user, fd_set *wfds)\n+{\n+\t/*\n+\t * Don't attempt to write if connection has failed.  This is one of\n+\t * the very few handlers still to run in delayed failure.\n+\t */\n+\tif (user->u_failure)\n+\t\treturn 0;\n+\t/* The file descrptor must be marked as writable. */\n+\tif (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, wfds))\n+\t\treturn 0;\n+\t/* Don't attempt to write if EOF has already been sent. */\n+\tif (user->u_black_out_eof)\n+\t\treturn 0;\n+\n+\tssize_t r = cbuffer_write_fd(user->u_black_out_buf, user->u_black_fd);\n+\tFD_CLR(user->u_black_fd, wfds);\n+\tif (r < 0 && is_fatal_error(errno)) {\n+\t\t/* Outbound connection faulted! */\n+\t\tcleanup_user(user, USER_LAYER4_ERROR, strerror(errno));\n+\t} else if (r > 0) {\n+\t\t/* Sent some data. */\n+\t}\n+\treturn 1;\n+}\n+\n+/* Send black out EOF if needed (using TLS if it is active) */\n+static int black_out_eof_handler(struct user *user)\n+{\n+\t/* Don't operate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate anymore if EOF has been sent outbound. */\n+\tif (user->u_black_out_eof || user->u_black_out_d_eof)\n+\t\treturn 0;\n+\t/* Don't activate if there can be more data from red in. */\n+\tif (user->u_red_assume_more || user->u_red_in_fd >= 0)\n+\t\treturn 0;\n+\t/* Don't activate if there can be more data from red error. */\n+\tif (user->u_red_err_fd >= 0)\n+\t\treturn 0;\n+\t/* Don't activate if there is more data in red input. */\n+\tif (cbuffer_used(user->u_red_in_buf))\n+\t\treturn 0;\n+\t/* Don't activate if there is more data in red error. */\n+\tif (cbuffer_used(user->u_red_err_buf))\n+\t\treturn 0;\n+\t/*\n+\t * If not in TLS mode, don't activate if there is data in send\n+\t * buffer.\n+\t */\n+\tif (!user->u_tls_session && cbuffer_used(user->u_black_out_buf))\n+\t\treturn 0;\n+\n+\tif (user->u_tls_session) {\n+\t\tint r;\n+\t\t/* Try to send the EOF at TLS level. */\n+\t\tr = gnutls_bye(user->u_tls_session, GNUTLS_SHUT_WR);\n+\t\tif (r == 0) {\n+\t\t\t/* Suceeded. Mark the connection as EOF'd. */\n+\t\t\tuser->u_black_out_d_eof = 1;\n+\t\t\treturn 1;\n+\t\t} else if (is_fatal_tls_error((int)r)) {\n+\t\t\t/* Fatal stream error! */\n+\t\t\treturn handle_tls_failure_code(user, (int)r, 0);\n+\t\t}\n+\t\t/* Otherwise we failed due to non-fatal TLS error. We'll try\n+\t\t   again soon. */\n+\t\treturn 0;\n+\t} else {\n+\t\t/* Try to send normal transport EOF. */\n+\t\tif (shutdown(user->u_black_fd, SHUT_WR) < 0) {\n+\t\t\t/* Failed, declare as stream error. */\n+\t\t\tcleanup_user(user, USER_LAYER4_ERROR,\n+\t\t\t\tstrerror(errno));\n+\t\t\treturn 0;\n+\t\t}\n+\t\tuser->u_black_out_eof = 1;\n+\t\treturn 1;\n+\t}\n+\treturn 0; /* Should not be here. */\n+}\n+\n+/* Send EOF to red output if needed. */\n+static int red_out_eof_handler(struct user *user)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate unless input EOF has been received. */\n+\tif (!user->u_black_in_eof && !user->u_black_in_d_eof)\n+\t\treturn 0;\n+\t/* Don't activate anymore if red out has been closed. */\n+\tif (user->u_red_out_fd < 0)\n+\t\treturn 0;\n+\t/* If not in TLS mode, don't activate if there is data in receive\n+\t   buffer. */\n+\tif (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))\n+\t\treturn 0;\n+\t/* Don't activate if there is more data in red output. */\n+\tif (cbuffer_used(user->u_red_out_buf))\n+\t\treturn 0;\n+\n+\tforce_close(user->u_red_out_fd);\n+\tuser->u_red_out_fd = -1;\n+\treturn 1;\n+}\n+\n+/* Send data to file descriptor connected to red output. */\n+static int red_out_handler(struct user *user, fd_set *wfds)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Red out must be writable fd. */\n+\tif (user->u_red_out_fd < 0 || !FD_ISSET(user->u_red_out_fd, wfds))\n+\t\treturn 0;\n+\n+\tssize_t r = cbuffer_write_fd(user->u_red_out_buf, user->u_red_out_fd);\n+\tFD_CLR(user->u_red_out_fd, wfds);\n+\tif (r < 0 && errno == EPIPE) {\n+\t\t/* EPIPE is treated as special type of EOF. We need to read\n+\t\t   EOFs from process. */\n+\t\tforce_close(user->u_red_out_fd);\n+\t\tuser->u_red_out_fd = -1;\n+\t\treturn 1;\n+\t} else if (r < 0 && is_fatal_error(errno)) {\n+\t\t/* Red out connection faulted! */\n+\t\tcleanup_user(user, USER_RED_FAILURE, strerror(errno));\n+\t\treturn 0;\n+\t} else if (r > 0) {\n+\t\t/* Sent some data to red output. */\n+\t\treturn 1;\n+\t}\n+\t/* Can't come here. */\n+\treturn 0;\n+}\n+\n+/* Recieve data from file descriptor connected to red input. */\n+static int red_in_handler(struct user *user, fd_set *rfds)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Red in must be readable fd. */\n+\tif (user->u_red_in_fd < 0 || !FD_ISSET(user->u_red_in_fd, rfds))\n+\t\treturn 0;\n+\n+\tssize_t r = cbuffer_read_fd(user->u_red_in_buf, user->u_red_in_fd);\n+\tFD_CLR(user->u_red_in_fd, rfds);\n+\tif (r < 0 && is_fatal_error(errno)) {\n+\t\t/* Inbound red connection faulted! */\n+\t\tcleanup_user(user, USER_RED_FAILURE, strerror(errno));\n+\t\treturn 0;\n+\t} else if (r == 0) {\n+\t\t/* Close it so we eventually send EOF. */\n+\t\tuser->u_red_assume_more = 0;\n+\t\tforce_close(user->u_red_in_fd);\n+\t\tuser->u_red_in_fd = -1;\n+\t\treturn 1;\n+\t} else if (r > 0) {\n+\t\t/* Received some data. */\n+\t\tuser->u_red_in_have_data = 1;\n+\n+\t\t/* Clear the error buffer. */\n+\t\tcbuffer_clear(user->u_red_err_buf);\n+\t}\n+\treturn 1;\n+}\n+\n+#define DUMMYBUFSIZE 256\n+\n+/* Receive data from file descriptor connected to red error. */\n+static int red_err_handler(struct user *user, fd_set *rfds)\n+{\n+\tssize_t r;\n+\tchar buf[DUMMYBUFSIZE];\n+\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Red err must be readable fd. */\n+\tif (user->u_red_err_fd < 0 || !FD_ISSET(user->u_red_err_fd, rfds))\n+\t\treturn 0;\n+\n+\tif (!user->u_red_in_have_data) {\n+\t\t/* Read the red error for real. */\n+\t\tr = cbuffer_read_fd(user->u_red_err_buf, user->u_red_err_fd);\n+\t} else {\n+\t\t/* Just do dummy read and discard the reuslts. */\n+\t\tr = read(user->u_red_err_fd, buf, DUMMYBUFSIZE);\n+\t}\n+\tFD_CLR(user->u_red_err_fd, rfds);\n+\tif (r < 0 && is_fatal_error(errno)) {\n+\t\t/* Inbound red connection faulted! */\n+\t\tcleanup_user(user, USER_RED_FAILURE, strerror(errno));\n+\t} else if (r == 0) {\n+\t\t/* Close it so we eventually send EOF. */\n+\t\tforce_close(user->u_red_err_fd);\n+\t\tuser->u_red_err_fd = -1;\n+\t} else if (r > 0) {\n+\t\t/*\n+\t\t * Received some data.\n+\t\t *\n+\t\t * HACK ALERT: Some systems seem to like to send these\n+\t\t * non-errors on error channel. Ignore those and treat\n+\t\t * them like data from stdout. (35 is length of that\n+\t\t * string).\n+\t\t */\n+\t\tconst char* tmsg = \"Initialized empty Git repository in\";\n+\t\tunsigned char tmp[35];\n+\t\tif (cbuffer_peek(user->u_red_err_buf, tmp, 35) >= 0 &&\n+\t\t\t!strncmp(tmsg, (char*)tmp, 35)) {\n+\t\t\tuser->u_red_in_have_data = 1;\n+\t\t\tcbuffer_clear(user->u_red_err_buf);\n+\t\t}\n+\t}\n+\treturn 1;\n+}\n+\n+/* Terminate the whole connection if needed EOFs have been seen. */\n+static int connection_eof_handler(struct user *user)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate if red out is still open. */\n+\tif (user->u_red_out_fd >= 0)\n+\t\treturn 0;\n+\t/* Don't activate unless output EOF has been asserted. */\n+\tif (!user->u_black_out_eof && !user->u_black_out_d_eof)\n+\t\treturn 0;\n+\t/* Don't assert in TLS mode unless output buffer is empty. */\n+\tif (user->u_tls_session && cbuffer_used(user->u_black_out_buf))\n+\t\treturn 0;\n+\n+\t/* Both half-connections have ended. End the entiere connection. */\n+\tcleanup_user(user, USER_CONNECTION_END, NULL);\n+\treturn 1;\n+}\n+\n+/*\n+ * Should operation take place given last operation failed practicular way?\n+ * Direction2 is 0 if last operation didn't fail, 1 if it failed due to\n+ * insufficient data to read, 2 if it failed due to insufficient space to\n+ * write.\n+ */\n+static int gnutls_blacks_activate2(struct user *user, int direction2)\n+{\n+\t/*\n+\t * If not attempted yet, or last time it was successful, attempt\n+\t * immediately again.\n+\t */\n+\tif (direction2 == 0)\n+\t\treturn 1;\n+\t/*\n+\t * If last time it failed due to insufficient read space, try\n+\t * again only if there is some data in read buffers now.\n+\t */\n+\tif (direction2 == 1) {\n+\t\tif (cbuffer_used(user->u_black_in_buf) != 0)\n+\t\t\treturn 1;\n+\t\telse if (!user->u_black_in_eof)\n+\t\t\treturn 0;\n+\t\telse if (user->u_tls_session && user->u_seen_input_data)\n+\t\t\tcleanup_user(user, USER_TLS_ERROR, \"Connection \"\n+\t\t\t\t\"closed unexpectedly\");\n+\t\telse if (user->u_tls_session)\n+\t\t\tcleanup_user(user, USER_LAYER4_ERROR, \"Connection \"\n+\t\t\t\t\"broke before any data was received\");\n+\t\telse\n+\t\t\treturn 0;\n+\t}\n+\t/*\n+\t * If last time it failed due to insufficient write space, try\n+\t * again only if there is some space in write buffers now.\n+\t */\n+\tif (direction2 == 2)\n+\t\treturn (cbuffer_free(user->u_black_out_buf) != 0);\n+\treturn 0;\n+}\n+\n+/* Copy or decrypt data from black input to red output. */\n+static int black_to_red_handler(struct user *user)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate if there's TLS but it isn't ready yet. */\n+\tif (user->u_tls_session && !user->u_tls_active)\n+\t\treturn 0;\n+\t/* Don't activate if there's no space in red out buffer. */\n+\tif (!cbuffer_free(user->u_red_out_buf))\n+\t\treturn 0;\n+\t/* Don't activate if TLS-level EOF has been received. */\n+\tif (user->u_black_in_d_eof)\n+\t\treturn 0;\n+\t/* Check that we don't fail like last time. */\n+\tif (!gnutls_blacks_activate2(user, user->u_want_read))\n+\t\treturn 0;\n+\n+\tif (!user->u_tls_session) {\n+\t\t/*\n+\t\t * no-TLS case. Just compute how much data we can move and\n+\t\t * then just move it from black in buffer to red out buffer.\n+\t\t */\n+\t\tsize_t amount;\n+\t\tamount = cbuffer_move_nolimit(user->u_red_out_buf,\n+\t\t\tuser->u_black_in_buf);\n+\t\tif (amount > 0) {\n+\t\t\t/* Ok, some data to move. Just move it. */\n+\t\t\tuser->u_want_read = 0;\n+\t\t\treturn 1;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * No data to transfer. Mark operation failed due to\n+\t\t\t * insufficient data to read.\n+\t\t\t */\n+\t\t\tuser->u_want_read = 1;\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tunsigned char *ptr;\n+\t\tsize_t size;\n+\t\tssize_t r;\n+\n+\t\t/*\n+\t\t * Compute the segment for writing data to. And if we do get\n+\t\t * such segment, receive data to it. The size == 0 case should\n+\t\t * not happen because we checked that there's space in red\n+\t\t * out buffer above.\n+\t\t */\n+\t\tcbuffer_fill_w_segment(user->u_red_out_buf, &ptr, &size);\n+\t\tr = gnutls_record_recv(user->u_tls_session, ptr, size);\n+\t\tif (r > 0) {\n+\t\t\t/* Received TLS data. */\n+\t\t\tcbuffer_commit_w_segment(user->u_red_out_buf, r);\n+\t\t\tuser->u_want_read = 0;\n+\t\t\treturn 1;\n+\t\t} else if (r == 0) {\n+\t\t\t/* Received TLS EOF. */\n+\t\t\tuser->u_black_in_d_eof = 1;\n+\t\t\tuser->u_want_read = 0;\n+\t\t\treturn 1;\n+\t\t} else if (is_fatal_tls_error((int)r)) {\n+\t\t\t/* Fatal TLS error. */\n+\t\t\treturn handle_tls_failure_code(user, (int)r, 0);\n+\t\t} else if (r < 0) {\n+\t\t\t/* Temporary read failure. */\n+\t\t\tuser->u_want_read = 1 + gnutls_record_get_direction(\n+\t\t\t\tuser->u_tls_session);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\t/* Can't really come here. */\n+\treturn 0;\n+}\n+\n+/* Copy or encrypt data from red input to black output. */\n+static int red_to_black_handler(struct user *user)\n+{\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate if TLS is present but not active. */\n+\tif (user->u_tls_session && !user->u_tls_active)\n+\t\treturn 0;\n+\t/* Require data in red in buffer. */\n+\tif (!cbuffer_used(user->u_red_in_buf))\n+\t\treturn 0;\n+\t/* Don't fail like last time. */\n+\tif (!gnutls_blacks_activate2(user, user->u_want_write))\n+\t\treturn 0;\n+\n+\tif (!user->u_tls_session) {\n+\t\t/*\n+\t\t * No-TLS case. Just find maximum amount of data to move and\n+\t\t * then move the data.\n+\t\t */\n+\t\tsize_t amount;\n+\t\tamount = cbuffer_move_nolimit(user->u_black_out_buf,\n+\t\t\tuser->u_red_in_buf);\n+\t\tif (amount > 0) {\n+\t\t\t/* Ok, some data to move. Just move it. */\n+\t\t\tuser->u_want_write = 0;\n+\t\t\treturn 1;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * No data to transfer. Mark operation failed due to\n+\t\t\t * insufficient space to write.\n+\t\t\t */\n+\t\t\tuser->u_want_write = 2;\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tunsigned char *ptr;\n+\t\tsize_t size;\n+\t\tssize_t r = 0;\n+\n+\t\t/*\n+\t\t * Compute the segment for reading data to. And if we do get\n+\t\t * such segment, send data from it. The size == 0 case should\n+\t\t * not happen because we checked that there's data in red\n+\t\t * in buffer above.\n+\t\t */\n+\t\tcbuffer_fill_r_segment(user->u_red_in_buf, &ptr, &size);\n+\t\tif (!user->u_want_write)\n+\t\t\t/* Last was success, just send new record. */\n+\t\t\tr = gnutls_record_send(user->u_tls_session, ptr,\n+\t\t\t\tsize);\n+\t\telse\n+\t\t\t/* Last time it failed, try to resend the record. */\n+\t\t\tr = gnutls_record_send(user->u_tls_session, NULL, 0);\n+\t\tif (r > 0) {\n+\t\t\t/* Sent some TLS data. */\n+\t\t\tcbuffer_commit_r_segment(user->u_red_in_buf, r);\n+\t\t\tuser->u_want_write = 0;\n+\t\t\treturn 1;\n+\t\t} else if (is_fatal_tls_error((int)r)) {\n+\t\t\t/* Fatal TLS error. */\n+\t\t\treturn handle_tls_failure_code(user, (int)r, 0);\n+\t\t} else if (r < 0) {\n+\t\t\t/* Temporary send failure. */\n+\t\t\tuser->u_want_write = 1 + gnutls_record_get_direction(user->u_tls_session);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\t/* Can't really come here. */\n+\treturn 0;\n+}\n+\n+static const char hexes[] = \"0123456789abcdef\";\n+\n+#define TMPBUFSIZE 256\n+\n+/* Copy or encrypt data from red error to black out. */\n+static int rederr_to_black_handler(struct user *user)\n+{\n+\tunsigned char hdrbuf[8];\n+\tunsigned char buffer[TMPBUFSIZE];\n+\tsize_t bufusage = 0;\n+\tsize_t pcktsize;\n+\tunsigned char *segstart;\n+\tsize_t seglen;\n+\tsize_t maxread;\n+\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate if TLS is not yet ready. */\n+\tif (user->u_tls_session && !user->u_tls_active)\n+\t\treturn 0;\n+\t/* Don't activate if there's no data in red error. */\n+\tif (!cbuffer_used(user->u_red_err_buf))\n+\t\treturn 0;\n+\t/* Don't activate if red in hasn't been closed. */\n+\tif (user->u_red_in_fd >= 0)\n+\t\treturn 0;\n+\t/* Don't activate if red error can have more data. */\n+\tif (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf))\n+\t\treturn 0;\n+\t/* Don't fail like last write. */\n+\tif (!gnutls_blacks_activate2(user, user->u_want_write))\n+\t\treturn 0;\n+\n+\t/* Safety hatch. Truncate error if too long. */\n+\tif (!cbuffer_free(user->u_red_err_buf) && user->u_red_err_fd >= 0) {\n+\t\tforce_close(user->u_red_err_fd);\n+\t\tuser->u_red_err_fd = -1;\n+\t}\n+\n+\t/* Fill the header. */\n+\tpcktsize = 8 + cbuffer_used(user->u_red_err_buf);\n+\thdrbuf[0] = hexes[pcktsize / 4096 % 16];\n+\thdrbuf[1] = hexes[pcktsize / 256 % 16];\n+\thdrbuf[2] = hexes[pcktsize / 16 % 16];\n+\thdrbuf[3] = hexes[pcktsize % 16];\n+\thdrbuf[4] = 'E';\n+\thdrbuf[5] = 'R';\n+\thdrbuf[6] = 'R';\n+\thdrbuf[7] = ' ';\n+\t/*\n+\t * If header hasn't been sent yet, copy the remainder of header to\n+\t * transfer buffer.\n+\t */\n+\tif (user->u_red_err_hdr_sent < 8) {\n+\t\tmemcpy(buffer, hdrbuf + user->u_red_err_hdr_sent,\n+\t\t\t8 - user->u_red_err_hdr_sent);\n+\t\tbufusage = (8 - user->u_red_err_hdr_sent);\n+\t}\n+\tmaxread = TMPBUFSIZE - bufusage;\n+\n+\t/*\n+\t * Request read segment out of red error. There's always data in\n+\t * red error buffer by checks above. Then copy as much data from\n+\t * it as possible.\n+\t */\n+\tcbuffer_fill_r_segment(user->u_red_err_buf, &segstart, &seglen);\n+\tif (maxread >= seglen) {\n+\t\tmemcpy(buffer + bufusage, segstart, seglen);\n+\t\tbufusage += seglen;\n+\t} else {\n+\t\tmemcpy(buffer + bufusage, segstart, maxread);\n+\t\tbufusage += maxread;\n+\t}\n+\n+\t/*\n+\t * Bufusage is always positive, since its either equal to seglen\n+\t * or maxread, and neither can be zero.\n+\t */\n+\tif (!user->u_tls_session && !user->u_red_in_have_data) {\n+\t\t/*\n+\t\t * Compute maximum amount of data that can be copied to\n+\t\t * send buffer (no TLS case).\n+\t\t */\n+\t\tbufusage = cbuffer_write_max(user->u_black_out_buf, buffer,\n+\t\t\tbufusage);\n+\t\tif (bufusage > 0)\n+\t\t\tuser->u_want_write = 0;\n+\t\telse {\n+\t\t\t/* No space, mark it failed due to write. */\n+\t\t\tuser->u_want_write = 2;\n+\t\t\treturn 0;\n+\t\t}\n+\t} else if (!user->u_red_in_have_data) {\n+\t\tssize_t r;\n+\n+\t\tif (!user->u_want_write)\n+\t\t\t/* Last was success, just send new record. */\n+\t\t\tr = gnutls_record_send(user->u_tls_session, buffer,\n+\t\t\t\tbufusage);\n+\t\telse\n+\t\t\t/* Last time it failed, try to resend the record. */\n+\t\t\tr = gnutls_record_send(user->u_tls_session, NULL, 0);\n+\t\tif (r > 0) {\n+\t\t\t/* Successfully sent data. Adjust bufusage. */\n+\t\t\tbufusage = r;\n+\t\t\tuser->u_want_write = 0;\n+\t\t} else if (is_fatal_tls_error((int)r)) {\n+\t\t\t/* Fatal TLS error. */\n+\t\t\treturn handle_tls_failure_code(user, (int)r, 0);\n+\t\t} else if (r < 0) {\n+\t\t\t/* Temporary failure. Mark the failure. */\n+\t\t\tuser->u_want_write = 1 + gnutls_record_get_direction(\n+\t\t\t\tuser->u_tls_session);\n+\t\t\tbufusage = 0;\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t/* Now bufusage is set to amount of bytes sent. ACK the data sent. */\n+\tif ((size_t)user->u_red_err_hdr_sent + bufusage < 8) {\n+\t\t/* Partially sent header. ACK the header sent. */\n+\t\tuser->u_red_err_hdr_sent += bufusage;\n+\t\tbufusage = 0;\n+\t\treturn 1;\n+\t} else {\n+\t\t/*\n+\t\t * Completely sent the header. ACK rest of it and substract\n+\t\t * it from actual data sent.\n+\t\t */\n+\t\tbufusage -= (8 - user->u_red_err_hdr_sent);\n+\t\tuser->u_red_err_hdr_sent = 8;\n+\t}\n+\t/* ACK the actual error data sent. */\n+\tif (bufusage > 0)\n+\t\tcbuffer_commit_r_segment(user->u_red_err_buf, bufusage);\n+\n+\treturn 1;\n+}\n+\n+/* Try to send delayed alert. */\n+static int tls_alert_handler(struct user *user)\n+{\n+\tint r;\n+\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't do this without delayed tls failure. */\n+\tif (!user->u_delay_tls_failure)\n+\t\treturn 0;\n+\t/* Don't fail like last time. */\n+\tif (!gnutls_blacks_activate2(user, user->u_want_hand))\n+\t\treturn 0;\n+\n+\tr = gnutls_alert_send(user->u_tls_session, GNUTLS_AL_FATAL,\n+\t\tuser->u_delay_alert);\n+\tif (r == 0) {\n+\t\t/* The user->u_delay_tls_failure may be 2 too. */\n+\t\tif (user->u_delay_tls_failure == 1)\n+\t\t\tdelay_cleanup_user(user, USER_TLS_HAND_ERROR,\n+\t\t\t\t\"Fatal alert sent\");\n+\t\telse\n+\t\t\tdelay_cleanup_user(user, USER_TLS_ERROR,\n+\t\t\t\t\"Fatal alert sent\");\n+\t\treturn 0;\n+\t} else if (is_fatal_tls_error(r)) {\n+\t\tconst char *err = gnutls_strerror_name((int)r);\n+\t\tdelay_cleanup_user(user, USER_TLS_ERROR, err);\n+\t\treturn 1;\n+\t} else if (r < 0) {\n+\t\t/* Still needs more attempts to send. */\n+\t\tuser->u_want_hand = 1 + gnutls_record_get_direction(\n+\t\t\tuser->u_tls_session);\n+\t\treturn 0;\n+\t}\n+\t/* Can't really come here. */\n+\treturn 0;\n+}\n+\n+/* Try to handshake TLS connection. */\n+static int handshake_handler(struct user *user)\n+{\n+\tint r;\n+\n+\t/* Don't activate on already failed connections. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Don't activate if TLS is ready or no TLS. */\n+\tif (user->u_tls_active || !user->u_tls_session)\n+\t\treturn 0;\n+\t/* Don't handshake anoymore with delayed TLS failure. */\n+\tif (user->u_delay_tls_failure)\n+\t\treturn 0;\n+\t/* Don't fail like last time. */\n+\tif (!gnutls_blacks_activate2(user, user->u_want_hand))\n+\t\treturn 0;\n+\n+\tr = gnutls_handshake(user->u_tls_session);\n+\tif (r == 0) {\n+\t\t/* Handshake completed, TLS ready. */\n+\t\tuser->u_want_hand = 0;\n+\t\tuser->u_tls_active = 1;\n+\t\treturn 1;\n+\t} else if (is_fatal_tls_error(r)) {\n+\t\t/* Fatal TLS error. */\n+\t\treturn handle_tls_failure_code(user, (int)r, 1);\n+\t} else if (r < 0) {\n+\t\t/* Still needs more handshaking. */\n+\t\tuser->u_want_hand = 1 + gnutls_record_get_direction(\n+\t\t\tuser->u_tls_session);\n+\t\treturn 0;\n+\t}\n+\t/* Can't really come here. */\n+\treturn 0;\n+}\n+\n+/*\n+ * Compare two timevals. Returns -1 if first is first, 0 if same, 1 otherwise\n+ */\n+static int tv_compare(const struct timeval *tv1, const struct timeval *tv2)\n+{\n+\tif (tv1->tv_sec == -1)\n+\t\treturn (tv2->tv_sec == -1) ? 0 : 1;\n+\tif (tv2->tv_sec == -1)\n+\t\treturn -1;\n+\tif (tv1->tv_sec > tv2->tv_sec)\n+\t\treturn 1;\n+\tif (tv1->tv_sec < tv2->tv_sec)\n+\t\treturn -1;\n+\tif (tv1->tv_usec > tv2->tv_usec)\n+\t\treturn 1;\n+\tif (tv1->tv_usec < tv2->tv_usec)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+/* Handle timeouts on connection. */\n+static int timeout_handler(struct user *user)\n+{\n+\tstruct timeval t;\n+\n+\tgettimeofday(&t, NULL);\n+\n+\t/* Don't activate on already failed connection. */\n+\tif (user->u_failure || user->u_delay_failure)\n+\t\treturn 0;\n+\t/* Is it time to activate? */\n+\tif (tv_compare(&user->u_deadline, &t) >= 0)\n+\t\treturn 0;\n+\n+\tcleanup_user(user, USER_TIMEOUT, \"Request timeout\");\n+\treturn 0;\n+}\n+\n+/* GnuTLS push function. */\n+static ssize_t gnutls_push_data(gnutls_transport_ptr_t ptr, const void *data,\n+\tsize_t size)\n+{\n+\tstruct user *user = (struct user*)ptr;\n+\n+\t/* Compute maximum transfer. */\n+\tif (size > cbuffer_free(user->u_black_out_buf))\n+\t\tsize = cbuffer_free(user->u_black_out_buf);\n+\n+\t/* If no data can be read, send EAGAIN. */\n+\tif (size == 0) {\n+\t\terrno = EAGAIN;\n+\t\treturn -1;\n+\t}\n+\n+\tcbuffer_write(user->u_black_out_buf, (unsigned char*)data, size);\n+\treturn size;\n+}\n+\n+/* GnuTLS pull function. */\n+static ssize_t gnutls_pull_data(gnutls_transport_ptr_t ptr, void *data,\n+\tsize_t size)\n+{\n+\tstruct user *user = (struct user*)ptr;\n+\n+\t/* Compute maximum transfer. */\n+\tif (size > cbuffer_used(user->u_black_in_buf))\n+\t\tsize = cbuffer_used(user->u_black_in_buf);\n+\n+\t/* If no data can be written, send EAGAIN. */\n+\tif (size == 0) {\n+\t\terrno = EAGAIN;\n+\t\treturn -1;\n+\t}\n+\n+\tcbuffer_read(user->u_black_in_buf, (unsigned char*)data, size);\n+\treturn size;\n+\n+}\n+\n+void user_configure_tls(struct user *user, gnutls_session_t session)\n+{\n+\tuser->u_tls_session = session;\n+\tuser->u_want_hand = 0;\n+\tuser->u_tls_active = 0;\n+\t/* Configure the TLS session transport level. */\n+\tgnutls_transport_set_ptr(session, user);\n+\tgnutls_transport_set_push_function(session, gnutls_push_data);\n+\tgnutls_transport_set_pull_function(session, gnutls_pull_data);\n+\tgnutls_transport_set_lowat(session, 0);\n+\t/*\n+\t * For security reasons, clear the red output as that can no\n+\t * longer be trusted.\n+\t */\n+\tcbuffer_clear(user->u_red_out_buf);\n+}\n+\n+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,\n+\tfd_set *wfds, struct timeval *deadline)\n+{\n+\t/*\n+\t * Execute all service handlers in case they alter what\n+\t * files should be waited on.\n+\t */\n+\tuser_service_nofd(user);\n+\n+\t/* Adjust deadline. */\n+\tif (tv_compare(deadline, &user->u_deadline) > 0)\n+\t\t*deadline = user->u_deadline;\n+\n+\tif (user->u_red_out_fd >= 0 && cbuffer_used(user->u_red_out_buf)) {\n+\t\t/* Red out is writable. */\n+\t\tFD_SET(user->u_red_out_fd, wfds);\n+\t\tif (*bound <= user->u_red_out_fd)\n+\t\t\t*bound = user->u_red_out_fd + 1;\n+\t}\n+\tif (user->u_black_fd >= 0 && !user->u_black_in_eof &&\n+\t\t/* Black in is readable. */\n+\t\tcbuffer_free(user->u_black_in_buf)) {\n+\t\tFD_SET(user->u_black_fd, rfds);\n+\t\tif (*bound <= user->u_black_fd)\n+\t\t\t*bound = user->u_black_fd + 1;\n+\t}\n+\t/* Intentionally bias red to black towards writing to black. */\n+\tif (user->u_black_fd >= 0 && !user->u_black_out_eof &&\n+\t\t/* Black out is writable. */\n+\t\tcbuffer_used(user->u_black_out_buf)) {\n+\t\tFD_SET(user->u_black_fd, wfds);\n+\t\tif (*bound <= user->u_black_fd)\n+\t\t\t*bound = user->u_black_fd + 1;\n+\t} else if (user->u_red_in_fd >= 0 &&\n+\t\tcbuffer_free(user->u_red_in_buf)) {\n+\t\t/* Red in is readable. */\n+\t\tFD_SET(user->u_red_in_fd, rfds);\n+\t\tif (*bound <= user->u_red_in_fd)\n+\t\t\t*bound = user->u_red_in_fd + 1;\n+\t}\n+\tif (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) {\n+\t\t/* Red err is readable. */\n+\t\tFD_SET(user->u_red_err_fd, rfds);\n+\t\tif (*bound <= user->u_red_err_fd)\n+\t\t\t*bound = user->u_red_err_fd + 1;\n+\t}\n+}\n+\n+void user_service(struct user *user, fd_set *rfds, fd_set *wfds)\n+{\n+\tint newr = 1;\n+\n+\t/* Do this until no service handler makes any progress. */\n+\twhile (newr) {\n+\t\tnewr = 0;\n+\t\tnewr = newr | black_in_handler(user, rfds);\n+\t\tnewr = newr | black_out_handler(user, wfds);\n+\t\tnewr = newr | red_in_handler(user, rfds);\n+\t\tnewr = newr | red_out_handler(user, wfds);\n+\t\tnewr = newr | red_err_handler(user, rfds);\n+\t\tnewr = newr | black_out_eof_handler(user);\n+\t\tnewr = newr | red_out_eof_handler(user);\n+\t\tnewr = newr | connection_eof_handler(user);\n+\t\tnewr = newr | black_to_red_handler(user);\n+\t\tnewr = newr | red_to_black_handler(user);\n+\t\tnewr = newr | rederr_to_black_handler(user);\n+\t\tnewr = newr | timeout_handler(user);\n+\t\tnewr = newr | handshake_handler(user);\n+\t\tnewr = newr | tls_alert_handler(user);\n+\t\tnewr = newr | process_delay_failure(user);\n+\t}\n+}\n+\n+void user_service_nofd(struct user *user)\n+{\n+\tfd_set rfds;\n+\tfd_set wfds;\n+\n+\t/* Clear the fd sets, we don't do I/O here. */\n+\tFD_ZERO(&rfds);\n+\tFD_ZERO(&wfds);\n+\tuser_service(user, &rfds, &wfds);\n+}\n+\n+gnutls_session_t user_get_tls(struct user *user)\n+{\n+\tif (user->u_tls_session && user->u_tls_active)\n+\t\treturn user->u_tls_session;\n+\telse\n+\t\treturn NULL;\n+}\n+\n+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err)\n+{\n+\tuser->u_red_in_fd = red_in;\n+\tuser->u_red_out_fd = red_out;\n+\tuser->u_red_err_fd = red_err;\n+\tif (red_in >= 0)\n+\t\tfcntl(red_in, F_SETFL, fcntl(red_in, F_GETFL) | O_NONBLOCK);\n+\tif (red_out >= 0)\n+\t\tfcntl(red_out, F_SETFL, fcntl(red_out, F_GETFL) | O_NONBLOCK);\n+\tif (red_err >= 0)\n+\t\tfcntl(red_err, F_SETFL, fcntl(red_err, F_GETFL) | O_NONBLOCK);\n+}\n+\n+void user_clear_red_io(struct user *user)\n+{\n+\tif (user->u_red_out_fd >= 0)\n+\t\tforce_close(user->u_red_out_fd);\n+\tuser->u_red_out_fd = -1;\n+}\n+\n+struct cbuffer *user_get_red_in(struct user *user)\n+{\n+\tif (user->u_red_in_fd >= 0)\n+\t\treturn NULL;\n+\telse\n+\t\treturn user->u_red_in_buf;\n+}\n+\n+struct cbuffer *user_get_red_out(struct user *user)\n+{\n+\tif (user->u_red_out_fd >= 0)\n+\t\treturn NULL;\n+\telse\n+\t\treturn user->u_red_out_buf;\n+}\n+\n+int user_get_failure(struct user *user)\n+{\n+\treturn user->u_failure;\n+}\n+\n+const char *user_get_error(struct user *user)\n+{\n+\treturn user->u_errmsg;\n+}\n+\n+void user_send_red_in_eof(struct user *user)\n+{\n+\tuser->u_red_assume_more = 0;\n+}\n+\n+struct cbuffer *user_get_red_err(struct user *user)\n+{\n+\tif (user->u_red_err_fd >= 0 || user->u_red_in_have_data)\n+\t\treturn NULL;\n+\telse\n+\t\treturn user->u_red_err_buf;\n+}\n+\n+void user_clear_deadline(struct user *user)\n+{\n+\tuser->u_deadline.tv_sec = -1;\n+}\n+\n+size_t round_up(size_t base, size_t divide)\n+{\n+\treturn base + (divide - base % divide) % divide;\n+}\n+\n+struct user *user_create(int black_fd, unsigned deadline_secs)\n+{\n+\tstruct timeval t;\n+\tsize_t offset[5];\n+#ifdef USE_TRAP_PAGING\n+\tsize_t trap[6];\n+#endif\n+\tint i;\n+\n+\tfor (i = 0; i < 5; i++)\n+\t\toffset[i] = i * BUFFERSIZE;\n+\n+\tfcntl(black_fd, F_SETFL, fcntl(black_fd, F_GETFL) | O_NONBLOCK);\n+\n+\t/* Compute the deadline field value. */\n+\tgettimeofday(&t, NULL);\n+\tt.tv_sec += deadline_secs;\n+\n+\t/* How much to allocate for buffers? */\n+\tint r = 0;\n+\tsize_t allocsize = 4 * BUFFERSIZE + ERR_BUFFERSIZE;\n+\n+\t/* Allocate the primary structure. */\n+\tstruct user *user = (struct user*)malloc(sizeof(struct user));\n+\tif (!user)\n+\t\treturn NULL;\n+\n+\t/* Allocate memory for buffer backing buffer. */\n+#ifdef USE_TRAP_PAGING\n+\tallocsize = 4 * round_up(BUFFERSIZE, getpagesize()) +\n+\t\tround_up(ERR_BUFFERSIZE, getpagesize()) +\n+\t\t6 * getpagesize();\n+\n+\tfor (i = 0; i < 5; i++) {\n+\t\toffset[i] = i * round_up(BUFFERSIZE, getpagesize()) + (i + 1) * getpagesize();\n+\t\ttrap[i] = offset[i] - getpagesize();\n+\t}\n+\ttrap[5] = allocsize - getpagesize();\n+\n+\tuser->u_buf_backing_size = allocsize;\n+\tuser->u_buf_backing = (unsigned char*)mmap(NULL,\n+\t\tuser->u_buf_backing_size,\n+\t\tPROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);\n+\tif (user->u_buf_backing == MAP_FAILED)\n+\t\tr = -1;\n+\tfor (i = 0; i < 6; i++)\n+\t\tif (mprotect(user->u_buf_backing + trap[i], getpagesize(),\n+\t\t\tPROT_NONE) < 0) {\n+\t\t\tmunmap(user->u_buf_backing, user->u_buf_backing_size);\n+\t\t\tr = -1;\n+\t\t\tbreak;\n+\t\t}\n+#else\n+\tuser->u_buf_backing = (unsigned char*)malloc(allocsize);\n+\tuser->u_buf_backing_size = 0;\n+\tif (!user->u_buf_backing)\n+\t\tr = -1;\n+#endif\n+\tif (r < 0) {\n+\t\tfree(user);\n+\t\treturn NULL;\n+\t}\n+\n+\tuser->u_black_in_eof = 0;\n+\tuser->u_black_out_eof = 0;\n+\tuser->u_black_in_d_eof = 0;\n+\tuser->u_black_out_d_eof = 0;\n+\tuser->u_want_read = 0;\n+\tuser->u_want_write = 0;\n+\tuser->u_want_hand = 0;\n+\tuser->u_red_assume_more = 1;\n+\tuser->u_red_in_have_data = 0;\n+\tuser->u_red_err_hdr_sent = 0;\n+\tuser->u_tls_active = 0;\n+\tuser->u_black_fd = black_fd;\n+\tuser->u_red_in_fd = -1;\n+\tuser->u_red_out_fd = -1;\n+\tuser->u_red_err_fd = -1;\n+\tuser->u_black_in_buf = cbuffer_create(user->u_buf_backing +\n+\t\toffset[0], BUFFERSIZE);\n+\tuser->u_black_out_buf = cbuffer_create(user->u_buf_backing +\n+\t\toffset[1], BUFFERSIZE);\n+\tuser->u_red_in_buf = cbuffer_create(user->u_buf_backing +\n+\t\toffset[2], BUFFERSIZE);\n+\tuser->u_red_out_buf = cbuffer_create(user->u_buf_backing +\n+\t\toffset[3], BUFFERSIZE);\n+\tuser->u_red_err_buf = cbuffer_create(user->u_buf_backing +\n+\t\toffset[4], ERR_BUFFERSIZE);\n+\tif (!user->u_black_in_buf || !user->u_black_out_buf ||\n+\t\t!user->u_red_in_buf || !user->u_red_out_buf ||\n+\t\t!user->u_red_err_buf) {\n+\t\t/* Failed to allocate memory. */\n+\t\tcbuffer_destroy(user->u_black_in_buf);\n+\t\tcbuffer_destroy(user->u_black_out_buf);\n+\t\tcbuffer_destroy(user->u_red_in_buf);\n+\t\tcbuffer_destroy(user->u_red_out_buf);\n+\t\tcbuffer_destroy(user->u_red_err_buf);\n+#ifdef USE_TRAP_PAGING\n+\t\tmunmap(user->u_buf_backing, user->u_buf_backing_size);\n+#else\n+\t\tfree(user->u_buf_backing);\n+#endif\n+\t\tfree(user);\n+\t\treturn NULL;\n+\t}\n+\tuser->u_deadline = t;\n+\tuser->u_failure = USER_STILL_ACTIVE;\n+\tuser->u_delay_failure = USER_STILL_ACTIVE;\n+\tuser->u_errmsg = NULL;\n+\tuser->u_tls_session = NULL;\n+\tuser->u_delay_tls_failure = 0;\n+\treturn user;\n+}\n+\n+\n+\n+void user_release(struct user *user)\n+{\n+\tif (!user->u_failure)\n+\t\tcleanup_user(user, USER_KILL, \"User session killed\");\n+\n+\tcbuffer_destroy(user->u_black_in_buf);\n+\tcbuffer_destroy(user->u_black_out_buf);\n+\tcbuffer_destroy(user->u_red_in_buf);\n+\tcbuffer_destroy(user->u_red_out_buf);\n+\tcbuffer_destroy(user->u_red_err_buf);\n+\tfree(user->u_errmsg);\n+\n+#ifdef USE_TRAP_PAGING\n+\tmunmap(user->u_buf_backing, user->u_buf_backing_size);\n+#else\n+\tfree(user->u_buf_backing);\n+#endif\n+\tfree(user);\n+}\n+\n+int user_tls_configured(struct user *user)\n+{\n+\treturn (user->u_tls_session != NULL);\n+}\n+\n+const char *user_explain_failure(int code)\n+{\n+\tswitch(code) {\n+\tcase USER_STILL_ACTIVE:\n+\t\treturn \"Still active\";\n+\tcase USER_CONNECTION_END:\n+\t\treturn \"Connection closed\";\n+\tcase USER_LAYER4_ERROR:\n+\t\treturn \"Transport error\";\n+\tcase USER_TLS_ERROR:\n+\t\treturn \"TLS error\";\n+\tcase USER_TLS_HAND_ERROR:\n+\t\treturn \"TLS handshake error\";\n+\tcase USER_KILL:\n+\t\treturn \"User killed\";\n+\tcase USER_RED_FAILURE:\n+\t\treturn \"Subprocess failure\";\n+\tcase USER_TIMEOUT:\n+\t\treturn \"Timeout\";\n+\tdefault:\n+\t\treturn \"Unknown error\";\n+\t}\n+}\n+\n+struct cbuffer *user_get_red_in_force(struct user *user)\n+{\n+\treturn user->u_red_in_buf;\n+}\n+\n+struct cbuffer *user_get_red_out_force(struct user *user)\n+{\n+\treturn user->u_red_out_buf;\n+}\n+\n+struct cbuffer *user_get_red_err_force(struct user *user)\n+{\n+\treturn user->u_red_err_buf;\n+}\n+\n+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert)\n+{\n+\tchar error_buffer[8192];\n+\tif (!user->u_tls_session)\n+\t\treturn;\n+\n+\tuser->u_delay_tls_failure = 1;\n+\tuser->u_delay_alert = alert;\n+\tsprintf(error_buffer, \"TLS alert forced: %s\",\n+\t\tgnutls_alert_get_name(alert));\n+\tuser->u_errmsg = strdup(error_buffer);\n+}\n+\n+int user_red_out_eofd(struct user *user)\n+{\n+\tif (user->u_red_out_fd >= 0)\n+\t\treturn 0;\n+\tif (user->u_tls_session && !user->u_black_in_d_eof)\n+\t\treturn 0;\n+\tif (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))\n+\t\treturn 0;\n+\tif (!user->u_tls_session && !user->u_black_in_eof)\n+\t\treturn 0;\n+\treturn 1;\n+}\ndiff --git a/git-over-tls/user.h b/git-over-tls/user.h\nnew file mode 100644\nindex 0000000..e921c98\n--- /dev/null\n+++ b/git-over-tls/user.h\n@@ -0,0 +1,357 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 2009\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+#ifndef _user__h__included__\n+#define _user__h__included__\n+\n+#include <gnutls/gnutls.h>\n+#include \"cbuffer.h\"\n+#include <stdint.h>\n+#include <sys/time.h>\n+\n+#ifdef __cplusplus\n+extern \"C\" {\n+#endif\n+\n+/*\n+ * Terminoloyy:\n+ *\tblack:\n+ *\t\tSide of user session connected to socket. Transports\n+ *\t\tunencrypted or TLS data between peers.\n+ *\tred:\n+ *\t\tSide of user session connected to server loop or to\n+ *\t\thelper program.\n+ *\tred input:\n+ *\t\tInput buffer unencrypted data is read from. May be connected\n+ *\t\tto file descriptor. In that case that file descriptor is\n+ *\t\tread for data.\n+ *\tred output:\n+ *\t\tOutput buffer decrypted data is written to. May be connected\n+ *\t\tto file descriptor. In that cse that file descriptor is\n+ *\t\twritten with the data.\n+ *\tred error:\n+ *\t\tInput buffer error input is read from. May be connecte to\n+ *\t\tfile descriptor, which is read for input data. If red input\n+ *\t\tfile descriptor has data succesfully read, the red error buffer\n+ *\t\tis cleared and any further error input is redirected to bit\n+ *\t\tbucket. If red input closes with red error having data, then\n+ *\t\tthat data is sent as ERR packet when red error has associated\n+ *\t\tfile descriptor closed.\n+ *\tred_in:\n+ *\t\tFile descriptor associated with red input.\n+ *\tred_out:\n+ *\t\tFile descriptor associated with red output.\n+ *\tred_err:\n+ *\t\tFile descriptor associated with red error.\n+ *\tdeadline:\n+ *\t\tTime to disconnect (some) client on or perform some other\n+ *\t\tservice.\n+ */\n+\n+/* Main session structure. Opaque type. */\n+struct user;\n+\n+/* Failure codes. */\n+/* User still active. */\n+#define USER_STILL_ACTIVE\t0\n+/* Connection normal end. */\n+#define USER_CONNECTION_END\t1\n+/* Transport error. */\n+#define USER_LAYER4_ERROR\t-1\n+/* TLS error while not handshaking. */\n+#define USER_TLS_ERROR\t\t-2\n+/* TLS handshake error. */\n+#define USER_TLS_HAND_ERROR\t-3\n+/* User killed. Never returned as failure code. */\n+#define USER_KILL\t\t-4\n+/* Red file descriptor I/O operation failure. */\n+#define USER_RED_FAILURE\t-5\n+/* User timed out. */\n+#define USER_TIMEOUT\t\t-6\n+\n+/*\n+ * Create new user session.\n+ *\n+ * Input:\n+ *\tblack_fd\tBlack fd. Must be socket.\n+ *\ttimeout_secs\tInitial timeout in seconds.\n+ *\n+ * Output:\n+ *\tReturn value\tNewly created session, or NULL on out of\n+ *\t\t\tmemory.\n+ */\n+struct user *user_create(int black_fd, unsigned timeout_secs);\n+\n+/*\n+ * Configure session to use TLS. The TLS session must be preconfigured\n+ * (credentials set, etc), but not handshaked.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ *\tsession\t\tTLS session to assign.\n+ */\n+void user_configure_tls(struct user *user, gnutls_session_t session);\n+\n+/*\n+ * Add current user session file descriptors that are ready to read or\n+ * write to file descriptor sets for read or write. Also update dead-\n+ * line if needed.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to handle.\n+ *\tbound\t\tCurrent file descriptor bound. 0 if there are\n+ *\t\t\tno file descriptors in either set.\n+ *\trfds\t\tRead file descriptor set.\n+ *\twfds\t\tWrite file descriptor set.\n+ *\tdeadline\tCurrent deadline for select.\n+ *\n+ * Output:\n+ *\tbound\t\tUpdated file descriptor bound.\n+ *\trfds\t\tUpdated read file descriptor set.\n+ *\twfds\t\tUpdated write file descriptor set.\n+ *\tdeadline\tUpdated deadline for select.\n+ */\n+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,\n+\tfd_set *wfds, struct timeval *deadline);\n+\n+/*\n+ * Service this user.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to handle.\n+ *\trfds\t\tReady to read file descriptors.\n+ *\twfds\t\tReady to write file descriptors.\n+ */\n+void user_service(struct user *user, fd_set *rfds, fd_set *wfds);\n+\n+/*\n+ * Service this user without doing any I/O\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to handle.\n+ */\n+void user_service_nofd(struct user *user);\n+\n+/*\n+ * Get failure class.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\t0 if connection is active, 1 if end\n+ *\t\t\tof connection, negative code if connection\n+ *\t\t\tended with error. See USER_* defintions.\n+ */\n+int user_get_failure(struct user *user);\n+\n+/*\n+ * Get explanation of failure code.\n+ *\n+ * Input:\n+ *\tcode\t\tThe failure code.\n+ *\n+ * Output:\n+ *\tReturn value\tString explaining the code. Do not\n+ *\t\t\tfree this.\n+ */\n+const char *user_explain_failure(int code);\n+\n+/*\n+ * Get more detailed error message to explain the failure.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session.\n+ *\n+ * Output:\n+ *\tReturn value\tError message or NULL if there is\n+ *\t\t\tno more detailed error message. Do\n+ *\t\t\tnot free this.\n+ *\n+ * Notes:\n+ *\t- Error message can be NULL or not independently of\n+ *\t  main failure status.\n+ */\n+const char *user_get_error(struct user *user);\n+\n+/*\n+ * Has TLS been configured?\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tNonzero if configured, zero if not.\n+ */\n+/* Returns 1 if TLS has been configured, 0 otherwise. */\n+int user_tls_configured(struct user *user);\n+\n+/*\n+ * Return TLS session associated with user session.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tIf TLS is configured and has handshaked, the TLS\n+ *\t\t\tsession. Otherwise NULL.\n+ */\n+gnutls_session_t user_get_tls(struct user *user);\n+\n+/*\n+ * Free user structure. If user is still active, disconnect user hard.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to release.\n+ */\n+void user_release(struct user *user);\n+\n+/*\n+ * Set red I/O file descriptors.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ *\tred_in\t\tRed input file descriptor, -1 for none.\n+ *\tred_out\t\tRed output file descritpor, -1 for none.\n+ *\tred_err\t\tRed error file descriptor, -1 for none.\n+ *\n+ * Notes:\n+ *\t- red_in and red_err must be read ends if present.\n+ *\t- red_out must be write end if present.\n+ */\n+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err);\n+\n+/*\n+ * Clear red I/O by closing red output and marking no red output file\n+ * descriptor. This may be neeeded, since red out can't close without\n+ * input to transfer.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ */\n+void user_clear_red_io(struct user *user);\n+\n+/*\n+ * Get red input buffer.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tIf red_in is set to none, the buffer, otherwise\n+ *\t\t\tNULL.\n+ */\n+struct cbuffer *user_get_red_in(struct user *user);\n+\n+/*\n+ * Get red output buffer.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tIf red_out is set to none, the buffer, otherwise\n+ *\t\t\tNULL.\n+ */\n+struct cbuffer *user_get_red_out(struct user *user);\n+\n+/*\n+ * Get red error buffer.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tIf red_err is set to none and no data has been\n+ *\t\t\treceived through red_in, the buffer, otherwise\n+ *\t\t\tNULL.\n+ */\n+struct cbuffer *user_get_red_err(struct user *user);\n+\n+/*\n+ * Clear deadline (don't generate timeout anymore).\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ */\n+void user_clear_deadline(struct user *user);\n+\n+/*\n+ * Send EOF to red input.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ *\n+ * Notes:\n+ *\t- Only works if red input has no file descriptor associated.\n+ *\t- EOF is automatically sent if red_in encounters EOF.\n+ */\n+void user_send_red_in_eof(struct user *user);\n+\n+/*\n+ * Force get red input buffer (even if it shouldn't be available).\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tRed input buffer.\n+ */\n+struct cbuffer *user_get_red_in_force(struct user *user);\n+\n+/*\n+ * Force get red output buffer (even if it shouldn't be available).\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tRed output buffer.\n+ */\n+struct cbuffer *user_get_red_out_force(struct user *user);\n+\n+/*\n+ * Force get red error buffer (even if it shouldn't be available).\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tRed error buffer.\n+ */\n+struct cbuffer *user_get_red_err_force(struct user *user);\n+\n+/*\n+ * Send fatal TLS alert.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to manipulate.\n+ *\talert\t\tThe alert to send.\n+ *\n+ * Notes:\n+ *\t- Ignored if no TLS has been configured.\n+ */\n+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert);\n+\n+/*\n+ * Has red output been EOF'd?\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\n+ * Output:\n+ *\tReturn value\tNonzero if red out can receive no more data and\n+ *\t\t\tnot connected to file descriptor, otherwise zero\n+ *\t\t\t(more data possible).\n+ */\n+int user_red_out_eofd(struct user *user);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\n-- \n1.6.6.102.gd6f8f.dirty\n"},{"id":"131470","messageId":"81b0412b1001130525j63f5879bx657436b418b128df@mail.gmail.com","threadId":"22200","inReplyTo":"1263388786-6880-3-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of 2)","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2010-01-13T13:25:29Z","receivedAt":"2010-01-13T13:25:29Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"On Wed, Jan 13, 2010 at 14:19, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> +char *copy_alloc(const char *str, size_t len)\n> +{\n> +       char *copy;\n> +\n> +       copy = xmalloc(len + 1);\n> +       copy[len] = 0;\n> +       strncpy(copy, str, len);\n> +       return copy;\n> +}\n\nSome know this code as strndup(3):\n\n  http://linux.die.net/man/3/strndup\n"},{"id":"131471","messageId":"fcaeb9bf1001130539p2971caavd101d46de9269769@mail.gmail.com","threadId":"22200","inReplyTo":"1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-01-13T13:39:12Z","receivedAt":"2010-01-13T13:39:12Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On 1/13/10, Ilari Liusvaara <ilari.liusvaara@elisanet.fi> wrote:\n> This is client-side support for Git-over-TLS (gits://). gits:// is\n>  version of git:// protocol layered on top of TLS (Transport Layer\n>  Security). If using TLS, it is autenticated transport supporing\n>  fetching, pushing and remote archive (plus special commands that\n>  have server-dependent meaning).\n>\n>  Needs GnuTLS, and adds new make option NO_GNUTLS that disables builing\n>  this code.\n>\n>  Supported underlying stream transports include TCP/IP, TCP/IPv6 and\n>  Unix domain sockets (including Linux abstract namespace).\n>\n>  Supported authentication mechanisms include passwords, keypairs and on\n>  some platforms Unix authentication if using unix domain sockets. Server\n>  is authenticated using keypair (hostkey).\n>\n>  The patch is split into two parts because it would be otherwise be\n>  too large for this list. Included are all the needed client side\n>  utilities (some of them run gpg internally).\n\nCan we rely on an external program, like stunnel, to do the job instead?\n-- \nDuy\n"},{"id":"131472","messageId":"20100113135753.GA7095@Knoppix","threadId":"22200","inReplyTo":"fcaeb9bf1001130539p2971caavd101d46de9269769@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T13:57:53Z","receivedAt":"2010-01-13T13:57:53Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 08:39:12PM +0700, Nguyen Thai Ngoc Duy wrote:\n>\n> Can we rely on an external program, like stunnel, to do the job instead?\n\nNo. The way authentication is done is very unusual. I don't think stunnel (or\nanything else) can deal with such modes. And the reason authentications are\ndone like they are done in order to minimize points of failure (getting\nreally annoyed at failure modes sshd introduced was one big reason for \nwriting this).\n\nI _definitely_ do not want to mess with X.509. And its not just about me\nmessing with it, it is also about pushing it to users.\n\nAnd one would need custom daemon anyway even if one used stunnel. \ngit-daemon just can't deal with authentication data.\n\n-Ilari\n"},{"id":"131473","messageId":"20100113141218.GA17687@inner.home.ulmdo.de","threadId":"22200","inReplyTo":"20100113135753.GA7095@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Andreas Krey","fromEmail":"a.krey@gmx.de","sentAt":"2010-01-13T14:12:18Z","receivedAt":"2010-01-13T14:12:18Z","isPatch":false,"sender":{"key":"a.krey@gmx.de","avatar":"https://avatars.githubusercontent.com/u/37810?v=4"},"body":"On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:\n...\n> And one would need custom daemon anyway even if one used stunnel. \n> git-daemon just can't deal with authentication data.\n\nIt doesn't need to, really. stunnel sets the environment variable\nSSL_CLIENT_DN with the distinguished name of the client certificate,\nwhich can be used in the hook scripts ('update') on the server.\n\n(I looked into that stuff once, but with the advent of smart-http(s)\nI pretty much lost any interest to try implementing gits:// via\nopenssl here, as it isn't yet an actual itch.)\n\nAndreas\n"},{"id":"131476","messageId":"20100113144745.GA7246@Knoppix","threadId":"22200","inReplyTo":"20100113141218.GA17687@inner.home.ulmdo.de","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T14:47:47Z","receivedAt":"2010-01-13T14:47:47Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 03:12:18PM +0100, Andreas Krey wrote:\n> On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:\n> ...\n> > And one would need custom daemon anyway even if one used stunnel. \n> > git-daemon just can't deal with authentication data.\n> \n> It doesn't need to, really. stunnel sets the environment variable\n> SSL_CLIENT_DN with the distinguished name of the client certificate,\n> which can be used in the hook scripts ('update') on the server.\n\nThat would be useless. Data about authenticated client needs to fed to\nauthorization decisions already before invoking git.\n\nAnd besides: Gits:// uses certificates as keypairs, which would make DN\ndata absolutely useless because it is untrustworthy. And adding PKI\nis way too complicated.\n\n> (I looked into that stuff once, but with the advent of smart-http(s)\n> I pretty much lost any interest to try implementing gits:// via\n> openssl here, as it isn't yet an actual itch.)\n\nThe authentication support for smart-http seems pretty bad (making the\nold mistake of not binding authentications). Of course, the same tricks\nas gits:// uses would work with https:// (its all TLS-level stuff), but\nno server or client does that.\n\n-Ilari\n"},{"id":"131488","messageId":"20100113161711.GB17687@inner.home.ulmdo.de","threadId":"22200","inReplyTo":"20100113144745.GA7246@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Andreas Krey","fromEmail":"a.krey@gmx.de","sentAt":"2010-01-13T16:17:11Z","receivedAt":"2010-01-13T16:17:11Z","isPatch":false,"sender":{"key":"a.krey@gmx.de","avatar":"https://avatars.githubusercontent.com/u/37810?v=4"},"body":"On Wed, 13 Jan 2010 16:47:47 +0000, Ilari Liusvaara wrote:\n...\n> > It doesn't need to, really. stunnel sets the environment variable\n> > SSL_CLIENT_DN with the distinguished name of the client certificate,\n> > which can be used in the hook scripts ('update') on the server.\n> \n> That would be useless. Data about authenticated client needs to fed to\n> authorization decisions already before invoking git.\n\nIf you don't want public read access then you need to wedge a script\nbetween stunnel and git itself that checks whether authentication is\npresent, yes.\n\n> And besides: Gits:// uses certificates as keypairs,\n\nMy gripe with this is that I would expect gits: to be the same\nas git: except that there is SSL underneath. git: does not have\nauthentication, so there should be none in gits: except what\nSSL provides. (And the auth via unix domain sockets would be\nusable for plain git: as well; there is no reason to encrypt\nlocal traffic?)\n\n(Is the unix auth via unix domain sockets part of GnuTLS?)\n\n> which would make DN\n> data absolutely useless because it is untrustworthy. And adding PKI\n> is way too complicated.\n\nThat's another story. I think that it would be possible nowadays\nto implement gits:// (in both ways) via core.gitproxy and a server-side\nwrapper program (stunnel or else), but that has the disadvantage of\nbeing unable to just provide a clone url without installing special\nsoftware besides git.\n\n...\n> The authentication support for smart-http seems pretty bad (making the\n> old mistake of not binding authentications).\n\nMind to explain 'binding authentications'?\n\nAndreas\n"},{"id":"131500","messageId":"20100113173610.GA7609@Knoppix","threadId":"22200","inReplyTo":"20100113161711.GB17687@inner.home.ulmdo.de","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T17:36:10Z","receivedAt":"2010-01-13T17:36:10Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 05:17:11PM +0100, Andreas Krey wrote:\n> On Wed, 13 Jan 2010 16:47:47 +0000, Ilari Liusvaara wrote:\n> \n> If you don't want public read access then you need to wedge a script\n> between stunnel and git itself that checks whether authentication is\n> present, yes.\n\nThat would violate layering badly. You need to decode the request\nfirst before you can authorize. And the git daemon does that.\n \n> > And besides: Gits:// uses certificates as keypairs,\n> \n> My gripe with this is that I would expect gits: to be the same\n> as git: except that there is SSL underneath. git: does not have\n> authentication, so there should be none in gits: except what\n> SSL provides.\n\nAll authentication in gits:// is at TLS (SSL) level or even lower.\n\n> (And the auth via unix domain sockets would be\n> usable for plain git: as well; there is no reason to encrypt\n> local traffic?)\n\nIn fact, git-remote-gits has unencrypted mode (should be compatible\nwith git-daemon). The reason its there is mainly for Unix domain\nsockets support and more advanced IPv6 support (interface indexes and\nnumeric addresses actually work).\n\n> (Is the unix auth via unix domain sockets part of GnuTLS?)\n\nNo, that server-only feature is part of the OS itself. In fact, it\nneeds no client-side support.\n\n> That's another story. I think that it would be possible nowadays\n> to implement gits:// (in both ways) via core.gitproxy and a server-side\n> wrapper program (stunnel or else), but that has the disadvantage of\n> being unable to just provide a clone url without installing special\n> software besides git.\n\nGIT_PROXY abuse? There are even better ways: smart transport remote\nhelpers (in next I think). Git can actually dispatch those (and yes,\nthat's exactly what this uses).\n\nAnd gits:// client is also buildable selfstanding. That would require\nnew client software, but its still nicer than GIT_PROXY abuse.\n\nAnother problem with GIT_PROXY abuse: How to deal with potentially\nmultiple custom protocols. Remote helpers can deal with that nicely.\n\n> ...\n> > The authentication support for smart-http seems pretty bad (making the\n> > old mistake of not binding authentications).\n> \n> Mind to explain 'binding authentications'?\n\nActually, that was little badly choosen term and not the true problem,\nbut the basic problem is that one peer has to trust the the other peer's\nauthentication for security of its own authentication.\n\nIn how authentications used by gits:// are designed, even if client doesn't\ndetect trying to authenticate with attacker, the attacker doesn't get any\nreplayable credentials without breaking crypto keys (as opposed to just\npasswords). This holds true even for password authentication (PAKE-type\nscheme is used).\n\nHTTP basic auth can be trivially sniffed if attacker can become other end\nof the encrypted link (crypto is by far the strongest link...). Digest auth\nis harder, but its essentially brute force against password (as opposed to\ntrying to break a key).\n\n\n-Ilari\n"},{"id":"131504","messageId":"20100113183520.GA23674@inner.home.ulmdo.de","threadId":"22200","inReplyTo":"20100113173610.GA7609@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Andreas Krey","fromEmail":"a.krey@gmx.de","sentAt":"2010-01-13T18:35:20Z","receivedAt":"2010-01-13T18:35:20Z","isPatch":false,"sender":{"key":"a.krey@gmx.de","avatar":"https://avatars.githubusercontent.com/u/37810?v=4"},"body":"On Wed, 13 Jan 2010 19:36:10 +0000, Ilari Liusvaara wrote:\n...\n> That would violate layering badly. You need to decode the request\n> first before you can authorize. And the git daemon does that.\n\nWell, yes. The script hackery would just decide between 'is allowed\nto read (or write commits)' and 'is allowed to modify refs'. On the\nother hand, git-daemon does not do fine-grained (read: per-branch)\naccess control, you'd only prevent pushing commits at all.\n\n...\n> > (Is the unix auth via unix domain sockets part of GnuTLS?)\n> \n> No, that server-only feature is part of the OS itself. In fact, it\n> needs no client-side support.\n\nOk, then I'll be really interested in the server-side support and\nthe man pages on the whole stuff. Especially in how this is going\nto be different from what ssh:// does or can do.\n\n...\n> GIT_PROXY abuse? There are even better ways: smart transport remote\n> helpers (in next I think). Git can actually dispatch those (and yes,\n> that's exactly what this uses).\n\nYeah, since the last mail I noticed that gitproxy is not quite what\nsome google hits suggest, and should have read the patch in some\nmore detail to find that gits is a remote helper.\n\nPlease consider my objections revoked, other than the claim that\nit could be done with stunnel, however ugly that would be.\n\n...\n> Actually, that was little badly choosen term and not the true problem,\n> but the basic problem is that one peer has to trust the the other peer's\n> authentication for security of its own authentication.\n\nI don't see how that would endanger the standard certificate auth in ssl\n(client or server).\n\n...\n> HTTP basic auth can be trivially sniffed if attacker can become other end\n> of the encrypted link\n\nOf course, you have another problem in that case...also I'd personally\nlike to rely on ssl client certificates when using https.\n\nAndreas\n"},{"id":"131510","messageId":"32541b131001131111u6bb0de01qe6cc1ecde5119084@mail.gmail.com","threadId":"22200","inReplyTo":"20100113135753.GA7095@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-13T19:11:14Z","receivedAt":"2010-01-13T19:11:14Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Wed, Jan 13, 2010 at 8:57 AM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> On Wed, Jan 13, 2010 at 08:39:12PM +0700, Nguyen Thai Ngoc Duy wrote:\n>>\n>> Can we rely on an external program, like stunnel, to do the job instead?\n>\n> No. The way authentication is done is very unusual. I don't think stunnel (or\n> anything else) can deal with such modes. And the reason authentications are\n> done like they are done in order to minimize points of failure (getting\n> really annoyed at failure modes sshd introduced was one big reason for\n> writing this).\n>\n> I _definitely_ do not want to mess with X.509. And its not just about me\n> messing with it, it is also about pushing it to users.\n>\n> And one would need custom daemon anyway even if one used stunnel.\n> git-daemon just can't deal with authentication data.\n\nIt sounds to me like you're doing two different things with this patch series:\n\n1) Adding additional authorization features (assuming the user is\nalready authenticated) to git-daemon\n\n2) Creating a TLS encryption layer with authentication support.\n\n#1 sounds like it could be its own patch series even if you don't have\n#2, and could be reviewed separately.\n\n#2 sounds like it is not even git-specific.  You've decided that ssh\nand stunnel don't fit your needs; what makes your solution not a\ngeneral TLS-based authentication layer, like stunnel but with\ndifferent certificate management?  If it's really a general layer,\nmaybe it should be distributed separately and git could be taught how\nto use it *or* stunnel (or ssh, as it does now) for its transport\nencryption/authentication.\n\nHave fun,\n\nAvery\n"},{"id":"131511","messageId":"20100113191802.GA8110@Knoppix","threadId":"22200","inReplyTo":"20100113183520.GA23674@inner.home.ulmdo.de","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T19:18:02Z","receivedAt":"2010-01-13T19:18:02Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 07:35:20PM +0100, Andreas Krey wrote:\n> On Wed, 13 Jan 2010 19:36:10 +0000, Ilari Liusvaara wrote:\n> \n> Ok, then I'll be really interested in the server-side support and\n> the man pages on the whole stuff. Especially in how this is going\n> to be different from what ssh:// does or can do.\n\nThat feature is grossly underdocumented (and also nonportable). Unix(7)\nshould document it, except that it doesn't for me (it documents that\nSO_PASSCRED takes a boolean, except that what the server implementation\npasses is something completely different).\n\nI found the intformation about how to forcibly get peer UID on Linux\nfrom one secure programming HOWTO.\n\nOne other software that I know uses similar stuff is D-BUS. AFAIK, SSH\ncan't do it.\n\nEssentially, it involves asking the kernel about UID the socket peer\nruns as (with local sockets, kernel knows that information).\n \n> Please consider my objections revoked, other than the claim that\n> it could be done with stunnel, however ugly that would be.\n\nOnly if you don't care about complexity introducing PKI would bring\n(yes, I read those manuals).\n\n> I don't see how that would endanger the standard certificate auth in ssl\n> (client or server).\n\nIt doesn't, but...\n\n> Of course, you have another problem in that case...also I'd personally\n> like to rely on ssl client certificates when using https.\n\nAnd how many (relative) use client ceritificates with SSL? Keypairs with SSH?\nWhy you think this is?\n\n-Ilari\n"},{"id":"131512","messageId":"32541b131001131130i6afae1a1xd3a70e5de5daa5cf@mail.gmail.com","threadId":"22200","inReplyTo":"20100113191802.GA8110@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-13T19:30:20Z","receivedAt":"2010-01-13T19:30:20Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Wed, Jan 13, 2010 at 2:18 PM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n>> Please consider my objections revoked, other than the claim that\n>> it could be done with stunnel, however ugly that would be.\n>\n> Only if you don't care about complexity introducing PKI would bring\n> (yes, I read those manuals).\n\nI think you're overstating the situation a bit here.  You can use\nX.509 certificates without setting up a full PKI.  Basically, an X.509\ncert is just a public key with some extra crud thrown into the data\nfile.  You could validate it using a PKI, but you could also validate\nit by checking the verbatim public key just like ssh does.  It's not\nelegant, but it works, and it's a worldwide standard.\n\n(I don't know if stunnel does this type of validation... but *I've*\ndone this with the openssl libraries, so I know it can be done.)\n\n>> Of course, you have another problem in that case...also I'd personally\n>> like to rely on ssl client certificates when using https.\n>\n> And how many (relative) use client ceritificates with SSL? Keypairs with SSH?\n> Why you think this is?\n\nAt least hundreds of thousands of people, including non-technical\npeople, use X.509 client certificates and SSL in various big\nindustries with high security requirements.  That's why every major\nweb browser supports them.  In contrast, ssh is only ever used by\ntechies, and there are fewer of those.  Of course, as techies our\ninformal observations might lead us to believe otherwise.\n\nFurthermore, how many people who really want ssh-style keypairs (and\nthus refuse to use X.509 and PKI) can't just use ssh as their git\ntransport?  I don't actually understand what the goal is here.\n\nHave fun,\n\nAvery\n"},{"id":"131513","messageId":"20100113194050.GA11688@inner.home.ulmdo.de","threadId":"22200","inReplyTo":"20100113191802.GA8110@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Andreas Krey","fromEmail":"a.krey@gmx.de","sentAt":"2010-01-13T19:40:50Z","receivedAt":"2010-01-13T19:40:50Z","isPatch":false,"sender":{"key":"a.krey@gmx.de","avatar":"https://avatars.githubusercontent.com/u/37810?v=4"},"body":"On Wed, 13 Jan 2010 21:18:02 +0000, Ilari Liusvaara wrote:\n...\n> That feature is grossly underdocumented (and also nonportable). Unix(7)\n> should document it, except that it doesn't for me (it documents that\n> SO_PASSCRED takes a boolean, except that what the server implementation\n> passes is something completely different).\n\nActually, I meant how you plan to map credentials (however obtained)\ninto allowed actions inside git-daemon (or the hooks).\n\n...\n> And how many (relative) use client ceritificates with SSL? Keypairs with SSH?\n> Why you think this is?\n\nBecause ssh is much more popular than ssl client auth. Obtaining client\ncertificates isn't much more complicated than getting an ssh account,\nonce you have scripts for the stuff ready.\n\nBut I wonder: When you want keypair auth, why not just use ssh?\nI didn't quite understand the use case yet, it seems. With ssh\nI have all the infrastructure like ssh-agent in place already;\nwith gits: (any kind of) it will be asked for sooner or later.\n\nAndreas\n"},{"id":"131520","messageId":"20100113200027.GA8207@Knoppix","threadId":"22200","inReplyTo":"32541b131001131111u6bb0de01qe6cc1ecde5119084@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T20:00:27Z","receivedAt":"2010-01-13T20:00:27Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 02:11:14PM -0500, Avery Pennarun wrote:\n> On Wed, Jan 13, 2010 at 8:57 AM, Ilari Liusvaara\n> <ilari.liusvaara@elisanet.fi> wrote:\n> It sounds to me like you're doing two different things with this patch series:\n> \n> 1) Adding additional authorization features (assuming the user is\n> already authenticated) to git-daemon\n>\n> 2) Creating a TLS encryption layer with authentication support.\n>\n> #1 sounds like it could be its own patch series even if you don't have\n> #2, and could be reviewed separately.\n\nThis series (really only one patch, only split because its large) only\ncontains client parts, not server ones (not seperately or via patching\ngit-daemon).\n\nAnd besides the daemon for gits:// was written from libraries up.\n\n> #2 sounds like it is not even git-specific.  You've decided that ssh\n> and stunnel don't fit your needs; what makes your solution not a\n> general TLS-based authentication layer, like stunnel but with\n> different certificate management? \n\nStunnel seems mainly \"tunnel stuff using SSL/TLS\" type thing and any\nsupport for auth in it seems afterthought. At least that's what I got\nfrom reading the manuals for it.\n\n> If it's really a general layer,\n> maybe it should be distributed separately and git could be taught how\n> to use it *or* stunnel (or ssh, as it does now) for its transport\n> encryption/authentication.\n\nThe way serverside works is quite different from git-daemon. On client\nside there are also some virtually inavoidable bidirectional couplings\n(breaks layering) between generic and git-specific parts.\n\nYes, the code is split into two layers, but both layers contain git-\nspecific details. And the lower layer is low-level transport control code,\nthat doesn't even know how to configure TLS connection (that is quite\nhigh-level task).\n\nAnd ssh:// is not git:// tunneled over SSH, the request passing is done\ndifferently.\n\n-Ilari\n"},{"id":"131522","messageId":"20100113200629.GA8383@Knoppix","threadId":"22200","inReplyTo":"32541b131001131130i6afae1a1xd3a70e5de5daa5cf@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T20:06:29Z","receivedAt":"2010-01-13T20:06:29Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 02:30:20PM -0500, Avery Pennarun wrote:\n> On Wed, Jan 13, 2010 at 2:18 PM, Ilari Liusvaara\n> <ilari.liusvaara@elisanet.fi> wrote:\n> \n> I think you're overstating the situation a bit here.  You can use\n> X.509 certificates without setting up a full PKI.  Basically, an X.509\n> cert is just a public key with some extra crud thrown into the data\n> file.  You could validate it using a PKI, but you could also validate\n> it by checking the verbatim public key just like ssh does.  It's not\n> elegant, but it works, and it's a worldwide standard.\n\nGrossly overcomplicated standard... ASN.1? And there are other usable\nstandards that can be used with TLS.\n\n> (I don't know if stunnel does this type of validation... but *I've*\n> done this with the openssl libraries, so I know it can be done.)\n\nAFAIK, it doesn't.\n \n> > And how many (relative) use client ceritificates with SSL? Keypairs with SSH?\n> > Why you think this is?\n> \n> At least hundreds of thousands of people, including non-technical\n> people, use X.509 client certificates and SSL in various big\n> industries with high security requirements. \n\nThat is: Epsilon.\n\n> That's why every major web browser supports them.\n\nSupports != is actually usable.\n\n> In contrast, ssh is only ever used by\n> techies, and there are fewer of those.  Of course, as techies our\n> informal observations might lead us to believe otherwise.\n\nMost of those that use git are techies anyway.\n\n> Furthermore, how many people who really want ssh-style keypairs (and\n> thus refuse to use X.509 and PKI) can't just use ssh as their git\n> transport?  I don't actually understand what the goal is here.\n\nAs said, I got fed up with failure modes of SSH.\n\n-Ilari\n"},{"id":"131523","messageId":"1263413564-sup-6775@ezyang","threadId":"22200","inReplyTo":"1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Edward Z. Yang","fromEmail":"ezyang@mit.edu","sentAt":"2010-01-13T20:13:35Z","receivedAt":"2010-01-13T20:13:35Z","isPatch":false,"sender":{"key":"ezyang@mit.edu","avatar":"https://gravatar.com/avatar/6aaa9d10a82c2cf3d676f1f9397c2ae05ee2534182eda446128c0fe7c04494ba?d=mp&s=160"},"body":"Excerpts from Ilari Liusvaara's message of Wed Jan 13 08:19:44 -0500 2010:\n> Supported authentication mechanisms include passwords, keypairs and on\n> some platforms Unix authentication if using unix domain sockets. Server\n> is authenticated using keypair (hostkey).\n\nAs a Git user, I'd like to say: it's about damn time! :-)\n\nCheers,\nEdward\n"},{"id":"131524","messageId":"32541b131001131213m75b4baefsc70a4cbf3c8431c8@mail.gmail.com","threadId":"22200","inReplyTo":"20100113200629.GA8383@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-13T20:13:40Z","receivedAt":"2010-01-13T20:13:40Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> On Wed, Jan 13, 2010 at 02:30:20PM -0500, Avery Pennarun wrote:\n>> That's why every major web browser supports them. [X.509 client-side certificates]\n>\n> Supports != is actually usable.\n\nLots of people use it.  That was my point.  If it weren't important,\nweb browser makers wouldn't bother putting it in; God knows they leave\nout a lot of other stuff that I'd like.\n\n>> Furthermore, how many people who really want ssh-style keypairs (and\n>> thus refuse to use X.509 and PKI) can't just use ssh as their git\n>> transport?  I don't actually understand what the goal is here.\n>\n> As said, I got fed up with failure modes of SSH.\n\nI think this is the answer that needs clarification.  What failure\nmodes are these?  ssh doesn't seem to fail for me.  And github.com\nseems to be working rather well with a huge number of users and ssh\nauthentication.\n\nIf you're upset at the failure modes of ssh, is it possible to fix ssh\ninstead of introducing Yet Another Tunneling Protocol?\n\nHave fun,\n\nAvery\n"},{"id":"131535","messageId":"20100113204732.GB8383@Knoppix","threadId":"22200","inReplyTo":"20100113194050.GA11688@inner.home.ulmdo.de","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T20:47:32Z","receivedAt":"2010-01-13T20:47:32Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 08:40:50PM +0100, Andreas Krey wrote:\n> On Wed, 13 Jan 2010 21:18:02 +0000, Ilari Liusvaara wrote:\n> ...\n> > That feature is grossly underdocumented (and also nonportable). Unix(7)\n> > should document it, except that it doesn't for me (it documents that\n> > SO_PASSCRED takes a boolean, except that what the server implementation\n> > passes is something completely different).\n> \n> Actually, I meant how you plan to map credentials (however obtained)\n> into allowed actions inside git-daemon (or the hooks).\n\nIts actually git-daemon2. And it doesn't authorize anything, only delegates\nthe authorization (e.g. to gitolite).\n \n> ...\n> > And how many (relative) use client ceritificates with SSL? Keypairs with SSH?\n> > Why you think this is?\n> \n> Because ssh is much more popular than ssl client auth. Obtaining client\n> certificates isn't much more complicated than getting an ssh account,\n> once you have scripts for the stuff ready.\n\nSSL client certificate usability is horrible. SSH keypairs are actually\nalmost usable.\n\n> But I wonder: When you want keypair auth, why not just use ssh?\n\nIIRC, I already have told at least twice...\n\n> I didn't quite understand the use case yet, it seems. With ssh\n> I have all the infrastructure like ssh-agent in place already;\n> with gits: (any kind of) it will be asked for sooner or later.\n\ngpg-agent can be used (since client uses gpg to protect the keys\nif needed).\n\n-Ilari\n"},{"id":"131537","messageId":"20100113210414.GA8535@Knoppix","threadId":"22200","inReplyTo":"32541b131001131213m75b4baefsc70a4cbf3c8431c8@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T21:04:14Z","receivedAt":"2010-01-13T21:04:14Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 03:13:40PM -0500, Avery Pennarun wrote:\n> On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara\n> \n> Lots of people use it.  That was my point.  If it weren't important,\n> web browser makers wouldn't bother putting it in; God knows they leave\n> out a lot of other stuff that I'd like.\n\nThere are two kinds of \"important\". Actually important for users and\nimportant for managers.\n \nThe latter tends to be implemented with much less real world need. And\none can usually tell which of those it was from usability of feature.\n\n> >> Furthermore, how many people who really want ssh-style keypairs (and\n> >> thus refuse to use X.509 and PKI) can't just use ssh as their git\n> >> transport?  I don't actually understand what the goal is here.\n> >\n> > As said, I got fed up with failure modes of SSH.\n> \n> I think this is the answer that needs clarification.  What failure\n> modes are these?  ssh doesn't seem to fail for me.  And github.com\n> seems to be working rather well with a huge number of users and ssh\n> authentication.\n\nThose failure modes tend to be show up at setup phase. But when they\nshow up, at worst I have seen ones that took hours to debug because\nof multitude of possible causes and no good information on what's\nwrong.\n\nAnd don't get me started about multi-key setups.\n\nSSH uses fixed sets of keys, which has inherent failure modes. And ssh\nserver tends to be worse than the client (Github can avoid the server\nfailure modes since they control the SSH server).\n\nBut not even github can avoid all the failure modes.\n\n> If you're upset at the failure modes of ssh, is it possible to fix ssh\n> instead of introducing Yet Another Tunneling Protocol?\n\nNo, those failure modes can't be solved in SSH.\n\n-Ilari\n"},{"id":"131539","messageId":"32541b131001131403u162bc6ebpd551ed19aadde7fb@mail.gmail.com","threadId":"22200","inReplyTo":"20100113210414.GA8535@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-13T22:03:45Z","receivedAt":"2010-01-13T22:03:45Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Wed, Jan 13, 2010 at 4:04 PM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> On Wed, Jan 13, 2010 at 03:13:40PM -0500, Avery Pennarun wrote:\n>> On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara\n>> > As said, I got fed up with failure modes of SSH.\n>>\n>> I think this is the answer that needs clarification.  What failure\n>> modes are these?  ssh doesn't seem to fail for me.  And github.com\n>> seems to be working rather well with a huge number of users and ssh\n>> authentication.\n>\n> Those failure modes tend to be show up at setup phase. But when they\n> show up, at worst I have seen ones that took hours to debug because\n> of multitude of possible causes and no good information on what's\n> wrong.\n>\n> And don't get me started about multi-key setups.\n>\n> SSH uses fixed sets of keys, which has inherent failure modes. And ssh\n> server tends to be worse than the client (Github can avoid the server\n> failure modes since they control the SSH server).\n>\n> But not even github can avoid all the failure modes.\n>\n>> If you're upset at the failure modes of ssh, is it possible to fix ssh\n>> instead of introducing Yet Another Tunneling Protocol?\n>\n> No, those failure modes can't be solved in SSH.\n\nThis is still not very illuminating.  How do you know your replacement\nwill not have these same failure modes?  If you solve your main\nannoyances with ssh, how do you know you won't introduce any new\nannoying failure modes?  *Why* can't ssh be fixed to solve the\nproblem?  Will I have to generate and manage yet another new set of\nkeys to use the new system?\n\nYou seem to be positioning your implementation as a competitor to\n*all* of ssh, https, and straight TLS (including stunnel), and\nmoreover, presenting it as superior to all three.  This is surely\npossible (they all suck differently), but it's going to be hard to\nconvince people.  And if your new security protocol *only* works with\ngit, it loses points automatically against other solutions.  (Even if\nssh is hard to set up, I've *already set it up*, so any new\nalternative starts with an immediate negative score.)\n\nHave fun,\n\nAvery\n"},{"id":"131540","messageId":"20100113220636.GC18625@spearce.org","threadId":"22200","inReplyTo":"32541b131001131403u162bc6ebpd551ed19aadde7fb@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2010-01-13T22:06:36Z","receivedAt":"2010-01-13T22:06:36Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Avery Pennarun <apenwarr@gmail.com> wrote:\n> possible (they all suck differently), but it's going to be hard to\n> convince people.  And if your new security protocol *only* works with\n> git, it loses points automatically against other solutions.  (Even if\n> ssh is hard to set up, I've *already set it up*, so any new\n> alternative starts with an immediate negative score.)\n\nYup.\n\nThis is where I have trouble with gits:// thus far.\n\nI already have SSH setup everywhere.  Even more so than I have\nGnuPG configured, because I need SSH for just about everything,\nand GnuPG for very little.  So, I might as well just use SSH.\n\n-- \nShawn.\n"},{"id":"131543","messageId":"20100113230023.GA9171@Knoppix","threadId":"22200","inReplyTo":"32541b131001131403u162bc6ebpd551ed19aadde7fb@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-13T23:00:24Z","receivedAt":"2010-01-13T23:00:24Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:\n> On Wed, Jan 13, 2010 at 4:04 PM, Ilari Liusvaara\n> \n> This is still not very illuminating.  How do you know your replacement\n> will not have these same failure modes? \n\nNo client-side fallbacks, key auth works pseudonymously. That takes\ncare of them pretty well.\n\n> If you solve your main\n> annoyances with ssh, how do you know you won't introduce any new\n> annoying failure modes? \n\nEnsuring that at least some information make back to client (presuably\nenough to figure out the problem).\n\nAnd then there are few failure modes that can't be helped no matter what\nI do (like mistaking protocol for P2P, git:// suffers from that as well).\n\n> *Why* can't ssh be fixed to solve the  problem?\n\nClient side fallbacks (may be desired or not!), service not being\nable to intervene on wheither to allow client or not in case of\nkeypair auth.\n\n>  Will I have to generate and manage yet another new set of\n> keys to use the new system?\n\nYes. \n\n> You seem to be positioning your implementation as a competitor to\n> *all* of ssh, https, and straight TLS (including stunnel),\n\nOnly to smart http://, smart https:// and ssh://. \n\n> and\n> moreover, presenting it as superior to all three.  This is surely\n> possible (they all suck differently), but it's going to be hard to\n> convince people.  And if your new security protocol *only* works with\n> git, it loses points automatically against other solutions. \n\nThe general design would be applicable to applications besides git, but\nthis implementation is git-specific.\n\nAnd making it work like stunnel? On server-side that could work, but\nnot on client side (and it would take quite extensive changes to \ngit-daemon).\n\n> (Even if\n> ssh is hard to set up, I've *already set it up*, so any new\n> alternative starts with an immediate negative score.)\n\nWell, if you like SSH more, then use ssh://...\n\n-Ilari\n"},{"id":"131549","messageId":"32541b131001131551m38ff02acpdd08d9f0562ac84d@mail.gmail.com","threadId":"22200","inReplyTo":"20100113230023.GA9171@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-13T23:51:03Z","receivedAt":"2010-01-13T23:51:03Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Wed, Jan 13, 2010 at 6:00 PM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:\n>> This is still not very illuminating.  How do you know your replacement\n>> will not have these same failure modes?\n>\n> No client-side fallbacks, key auth works pseudonymously. That takes\n> care of them pretty well.\n\nPerhaps I'm being dense, but I don't understand what you mean by\neither of those.\n\n>> If you solve your main\n>> annoyances with ssh, how do you know you won't introduce any new\n>> annoying failure modes?\n>\n> Ensuring that at least some information make back to client (presuably\n> enough to figure out the problem).\n\nUnfortunately revealing information like that is a compromise; it\nhelps attackers as well as legitimate users.  It's the same reason\nlogin prints \"invalid username or password\" instead of choosing\nbetween \"invalid username\" and \"invalid password.\"\n\nIf you reveal more information than ssh, you'll be accused of being\nless secure.  And since the purpose of your protocol is security, this\nis a problem.\n\n>> *Why* can't ssh be fixed to solve the  problem?\n>\n> Client side fallbacks (may be desired or not!), service not being\n> able to intervene on wheither to allow client or not in case of\n> keypair auth.\n\nI don't understand that answer.  Couldn't ssh be patched to do\nwhatever you want?  Particularly if it's just better (optional)\ndiagnostics, you'd think someone would accept the patch for that.\n\n>>  Will I have to generate and manage yet another new set of\n>> keys to use the new system?\n>\n> Yes.\n\nOuch.\n\n>> (Even if\n>> ssh is hard to set up, I've *already set it up*, so any new\n>> alternative starts with an immediate negative score.)\n>\n> Well, if you like SSH more, then use ssh://...\n\nI'm just looking for a justification for why I *shouldn't* like ssh\nmore.  Is the only reason the fact that it might be easier to\ninitially configure the key exchange?\n\nAvery\n"},{"id":"131620","messageId":"20100114085124.GA10298@Knoppix","threadId":"22200","inReplyTo":"32541b131001131551m38ff02acpdd08d9f0562ac84d@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-14T08:51:25Z","receivedAt":"2010-01-14T08:51:25Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Wed, Jan 13, 2010 at 06:51:03PM -0500, Avery Pennarun wrote:\n> On Wed, Jan 13, 2010 at 6:00 PM, Ilari Liusvaara\n> <ilari.liusvaara@elisanet.fi> wrote:\n> > On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:\n> >\n> > No client-side fallbacks, key auth works pseudonymously. That takes\n> > care of them pretty well.\n> \n> Perhaps I'm being dense, but I don't understand what you mean by\n> either of those.\n\nThe client tries only one auth method instead of potentially trying\nmultiple. Witness the 'use verbose mode and check if it uses the key'\ntype stuff.\n\nWith keypair auth, the server can accept arbitrary (valid) keypair,\nbut only limited set have special priviledges -> Cuts down significantly\non \"why this doesn't accept the key\" problems (the keyid is usually\nprinted on denied access).\n\n> >> If you solve your main\n> >> annoyances with ssh, how do you know you won't introduce any new\n> >> annoying failure modes?\n> >\n> > Ensuring that at least some information make back to client (presuably\n> > enough to figure out the problem).\n> \n> Unfortunately revealing information like that is a compromise; it\n> helps attackers as well as legitimate users.  It's the same reason\n> login prints \"invalid username or password\" instead of choosing\n> between \"invalid username\" and \"invalid password.\"\n\nYeah. Sometimes one must chose balance between being helpful to users\nand being helpful for attackers.\n\n> >> *Why* can't ssh be fixed to solve the  problem?\n> >\n> > Client side fallbacks (may be desired or not!), service not being\n> > able to intervene on wheither to allow client or not in case of\n> > keypair auth.\n> \n> I don't understand that answer.  Couldn't ssh be patched to do\n> whatever you want?  Particularly if it's just better (optional)\n> diagnostics, you'd think someone would accept the patch for that.\n\nOpenSSH? With the level of paranoia in it, I'd say good luck. And\nit's not just client, its the server also (and especially the\nserver).\n\n> >>  Will I have to generate and manage yet another new set of\n> >> keys to use the new system?\n> >\n> > Yes.\n> \n> Ouch.\n\nWell, usually that means one keypair to generate and exchanging\nkeyids.\n\nAnd if you host the repo system too, you would get second key anyway\n(and SSH is not too good at handling multiple keys).\n\n> > Well, if you like SSH more, then use ssh://...\n> \n> I'm just looking for a justification for why I *shouldn't* like ssh\n> more.  Is the only reason the fact that it might be easier to\n> initially configure the key exchange?\n\nAnd besides, gits:// is for host multiple repos type stuff, not for\nprivate repos on your account (use the ssh:// for those, and there the\nfailure modes of SSH matter much less).\n\n-Ilari\n"},{"id":"131674","messageId":"32541b131001141246o1f5ce816gc4a26b81343aaa2d@mail.gmail.com","threadId":"22200","inReplyTo":"20100114085124.GA10298@Knoppix","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-01-14T20:46:56Z","receivedAt":"2010-01-14T20:46:56Z","isPatch":false,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Thu, Jan 14, 2010 at 3:51 AM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> The client tries only one auth method instead of potentially trying\n> multiple. Witness the 'use verbose mode and check if it uses the key'\n> type stuff.\n\nI believe this is a limitation of the client, not of the protocol.  So\na patch to the ssh client could fix this.\n\n> OpenSSH? With the level of paranoia in it, I'd say good luck. And\n> it's not just client, its the server also (and especially the\n> server).\n\nBut you could fork it if you wanted.  It's about as easy to convince\nme to install a different version of ssh than to install\nyet-another-security-server.  (In fact, it might be easier to get me\nto put in a patched openssh; at least then I can trust that it's\nmostly openssh, and examine just what's different in your version.)\n\n> And if you host the repo system too, you would get second key anyway\n> (and SSH is not too good at handling multiple keys).\n\nI'm not really sure about this.  ssh-add seems pretty easy.\n\nHave fun,\n\nAvery\n"},{"id":"131685","messageId":"20100114230809.GA15928@Knoppix","threadId":"22200","inReplyTo":"32541b131001141246o1f5ce816gc4a26b81343aaa2d@mail.gmail.com","subject":"Re: [RFC 0/2] Git-over-TLS (gits://) client side support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-01-14T23:08:09Z","receivedAt":"2010-01-14T23:08:09Z","isPatch":false,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"On Thu, Jan 14, 2010 at 03:46:56PM -0500, Avery Pennarun wrote:\n> On Thu, Jan 14, 2010 at 3:51 AM, Ilari Liusvaara\n> <ilari.liusvaara@elisanet.fi> wrote:\n\n> > The client tries only one auth method instead of potentially trying\n> > multiple. Witness the 'use verbose mode and check if it uses the key'\n> > type stuff.\n> \n> I believe this is a limitation of the client, not of the protocol.  So\n> a patch to the ssh client could fix this.\n\nThis is also about interfaces to user. It effectively can't be patched.\n\n<forking OpenSSH>\n\nGet real.\n\n> > And if you host the repo system too, you would get second key anyway\n> > (and SSH is not too good at handling multiple keys).\n> \n> I'm not really sure about this.  ssh-add seems pretty easy.\n \nAnyway, two SSH keys in interactive use means which to use has to be selected,\none doesn't.\n\n-Ilari\n"}]}