{"thread":{"id":"23654","subject":"[PATCH 0/3] git over TLS (gits://) support","startedAt":"2010-05-01T17:09:48Z","lastAt":"2010-05-03T17:58:05Z","messageCount":9,"participants":["Ilari Liusvaara","Sverre Rabbelier","Andreas Ericsson","Tay Ray Chuan","Michael J Gruber","Avery Pennarun"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"140684","messageId":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"23654","inReplyTo":null,"subject":"[PATCH 0/3] git over TLS (gits://) support","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-05-01T17:09:48Z","receivedAt":"2010-05-01T17:09:48Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"This is newer version of gits:// client side support.\n\ngit over TLS (gits://) is authenticated smart transport that passes\ngit:// protocol over TLS.\n\nSupported authentication modes:\n- Username/Password (SRP)\n- OpenPGP keypairs\n- SSH keypairs (/w ssh-agent support)\n\nThe patch is split to three parts due to its size. Especially the\nauthentication parts are large (and the parts used to deal with\nthe connection with server are also large).\n\nThe repository (containing server code and selfstanding version of\nclient code is at): http://repo.or.cz/w/git-daemon2.git/\n\nIlari Liusvaara (3):\n  git over TLS (gits://) support (part 1)\n  git over TLS (gits://) support (part 2)\n  git over TLS (gits://) support (part 3)\n\n Makefile                                     |   25 +-\n git-over-tls/.gitignore                      |    5 +\n git-over-tls/Makefile                        |   45 +\n git-over-tls/base64.c                        |  171 +++\n git-over-tls/base64.h                        |   21 +\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                       |  275 +++++\n git-over-tls/connect.h                       |   17 +\n git-over-tls/genkeypair.c                    |   38 +\n git-over-tls/gensrpverifier.c                |  377 ++++++\n git-over-tls/getkeyid.c                      |  179 +++\n git-over-tls/gits-send-special-command       |   22 +\n git-over-tls/gits-send-special-command-nourl |   23 +\n git-over-tls/home.c                          |  229 ++++\n git-over-tls/home.h                          |   71 ++\n git-over-tls/hostkey.c                       |   81 ++\n git-over-tls/hostkey.h                       |   15 +\n git-over-tls/keypairs.c                      |   60 +\n git-over-tls/keypairs.h                      |   16 +\n git-over-tls/main.c                          |  684 +++++++++++\n git-over-tls/misc.c                          |   15 +\n git-over-tls/misc.h                          |   27 +\n git-over-tls/mkcert.c                        |  474 ++++++++\n git-over-tls/pem.c                           |  362 ++++++\n git-over-tls/pem.h                           |   16 +\n git-over-tls/pem_decrypt.c                   |  203 ++++\n git-over-tls/pem_decrypt.h                   |    9 +\n git-over-tls/prompt.c                        |  100 ++\n git-over-tls/prompt.h                        |   18 +\n git-over-tls/srp_askpass.c                   |  110 ++\n git-over-tls/srp_askpass.h                   |   16 +\n git-over-tls/ssh.c                           |  635 ++++++++++\n git-over-tls/ssh.h                           |   18 +\n git-over-tls/ssh_localkey.c                  |  435 +++++++\n git-over-tls/ssh_localkey.h                  |   18 +\n git-over-tls/user.c                          | 1597 ++++++++++++++++++++++++++\n git-over-tls/user.h                          |  367 ++++++\n 40 files changed, 7913 insertions(+), 3 deletions(-)\n create mode 100644 git-over-tls/.gitignore\n create mode 100644 git-over-tls/Makefile\n create mode 100644 git-over-tls/base64.c\n create mode 100644 git-over-tls/base64.h\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 100755 git-over-tls/gits-send-special-command-nourl\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/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/pem.c\n create mode 100644 git-over-tls/pem.h\n create mode 100644 git-over-tls/pem_decrypt.c\n create mode 100644 git-over-tls/pem_decrypt.h\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/ssh.c\n create mode 100644 git-over-tls/ssh.h\n create mode 100644 git-over-tls/ssh_localkey.c\n create mode 100644 git-over-tls/ssh_localkey.h\n create mode 100644 git-over-tls/user.c\n create mode 100644 git-over-tls/user.h\n"},{"id":"140685","messageId":"1272733791-11341-2-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"23654","inReplyTo":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[PATCH 1/3] git over TLS (gits://) support (part 1)","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-05-01T17:09:49Z","receivedAt":"2010-05-01T17:09:49Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n Makefile               |   25 +-\n git-over-tls/Makefile  |   45 ++\n git-over-tls/cbuffer.c |  504 +++++++++++++++\n git-over-tls/cbuffer.h |  304 +++++++++\n git-over-tls/user.c    | 1597 ++++++++++++++++++++++++++++++++++++++++++++++++\n git-over-tls/user.h    |  367 +++++++++++\n 6 files changed, 2839 insertions(+), 3 deletions(-)\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/user.c\n create mode 100644 git-over-tls/user.h\n\ndiff --git a/Makefile b/Makefile\nindex 910f471..b11fed4 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@@ -295,7 +298,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@@ -1284,6 +1287,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@@ -1433,6 +1439,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@@ -1456,7 +1466,7 @@ endif\n ALL_CFLAGS += $(BASIC_CFLAGS)\n ALL_LDFLAGS += $(BASIC_LDFLAGS)\n \n-export TAR INSTALL DESTDIR SHELL_PATH\n+export TAR INSTALL DESTDIR SHELL_PATH ALL_CFLAGS\n \n \n ### Build rules\n@@ -1476,6 +1486,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@@ -1969,6 +1982,9 @@ install: all\n \t$(INSTALL) -m 644 $(SCRIPT_LIB) '$(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@@ -2101,6 +2117,9 @@ ifndef NO_PERL\n \t$(RM) gitweb/gitweb.cgi gitweb/gitweb.min.*\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/Makefile b/git-over-tls/Makefile\nnew file mode 100644\nindex 0000000..69d1d33\n--- /dev/null\n+++ b/git-over-tls/Makefile\n@@ -0,0 +1,45 @@\n+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))\n+helper = git-remote-gits\n+programs = gits-get-key-id gits-generate-keypair\n+scripts = gits-send-special-command gits-send-special-command-nourl\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+ssh_deps = ssh.o ssh_localkey.o pem.o base64.o home.o pem_decrypt.o srp_askpass.o prompt.o user.o cbuffer.o misc.o\n+\n+all: $(programs) $(helper)\n+\n+git-remote-gits: main.o user.o cbuffer.o keypairs.o hostkey.o certificate.o connect.o $(ssh_deps)\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+gits-get-key-id: getkeyid.o certificate.o $(ssh_deps)\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+\n+ifndef NO_SRP\n+gits-generate-srp-verifier: gensrpverifier.o prompt.o home.o\n+\t$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls\n+endif\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) $(ALL_CFLAGS) -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/user.c b/git-over-tls/user.c\nnew file mode 100644\nindex 0000000..bcfeac1\n--- /dev/null\n+++ b/git-over-tls/user.c\n@@ -0,0 +1,1597 @@\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 <stdint.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.\n+\t * 1 => Read was blocked by insufficient input data.\n+\t * 2 => Read was blocked by insufficient output space.\n+\t * 3 => No read attempted.\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.\n+\t * 1 => Write was blocked by insufficient input data.\n+\t * 2 => Write was blocked by insufficient output space.\n+\t * 3 => No write attempted.\n+\t */\n+\t\tu_want_write : 2,\n+\t/*\n+\t * Result of last handshake attempt.\n+\t * 0 => Handshake was successful.\n+\t * 1 => Handshake was blocked by insufficient input data.\n+\t * 2 => Handshake was blocked by insufficient output space.\n+\t * 3 => No handshake attempted.\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+\t/* Transfer counters. */\n+\tuint64_t u_raw_tx;\n+\tuint64_t u_raw_rx;\n+\tuint64_t u_decoded_tx;\n+\tuint64_t u_decoded_rx;\n+\tstruct timeval u_last_rx;\n+\tstruct timeval u_last_tx;\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. If TLS is enabled, also assume output EOF. */\n+\t\tuser->u_black_in_eof = 1;\n+\t\tif (user->u_tls_session)\n+\t\t\tuser->u_black_out_eof = 1;\n+\t} else if (r > 0) {\n+\t\t/* Received some data. */\n+\t\tuser->u_raw_rx += r;\n+\t\tuser->u_seen_input_data = 1;\n+\t\tgettimeofday(&user->u_last_rx, NULL);\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\tuser->u_raw_tx += r;\n+\t\tgettimeofday(&user->u_last_tx, NULL);\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 || direction2 == 3)\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\tuser->u_decoded_rx += amount;\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_decoded_rx += 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\tuser->u_decoded_tx += amount;\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 == 0 || user->u_want_write == 3)\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_decoded_tx += 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\t\tuser->u_decoded_tx += bufusage;\n+\t\t} else {\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 == 0 || user->u_want_write == 3)\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_decoded_tx += bufusage;\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 (No-TLS case). */\n+static int tls_alert_handler_no_tls(struct user *user)\n+{\n+\t//Count it as immediates success.\n+\tcleanup_user(user, USER_KILL, \"Would send fatal alert\");\n+\treturn 0;\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/* Handle no-TLS case. */\n+\tif (!user->u_tls_session)\n+\t\treturn tls_alert_handler_no_tls(user);\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/* Pretend that errored connections have immediate deadline. */\n+\tif (user->u_failure)\n+\t\tdeadline->tv_sec = 0;\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 = 3;\n+\tuser->u_want_write = 3;\n+\tuser->u_want_hand = 3;\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+\tuser->u_raw_tx = 0;\n+\tuser->u_raw_rx = 0;\n+\tuser->u_decoded_tx = 0;\n+\tuser->u_decoded_rx = 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+\n+\tif (user->u_delay_tls_failure)\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+}\n+\n+#define NAME_SPACE 60\n+\n+void print_name(char *buffer, const char *name)\n+{\n+\tunsigned i;\n+\tif(strlen(name) >= NAME_SPACE) {\n+\t\tstrncpy(buffer, name, NAME_SPACE - 4);\n+\t\tfor(i = NAME_SPACE - 4; i < NAME_SPACE - 1; i++)\n+\t\t\tbuffer[NAME_SPACE - i] = '.';\n+\t} else {\n+\t\tstrcpy(buffer, name);\n+\t\tfor(i = strlen(name); i < NAME_SPACE - 1; i++)\n+\t\t\tbuffer[i] = ' ';\n+\t}\n+\tbuffer[NAME_SPACE - 1] = ' ';\n+}\n+\n+void print_boolean(FILE *filp, const char *name, unsigned value)\n+{\n+\tchar buffer[NAME_SPACE + 10];\n+\tprint_name(buffer, name);\n+\tif (value)\n+\t\tstrcpy(buffer + NAME_SPACE, \"Set\");\n+\telse\n+\t\tstrcpy(buffer + NAME_SPACE, \"Clear\");\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void print_status(FILE *filp, const char *name, unsigned value)\n+{\n+\tchar buffer[NAME_SPACE + 40];\n+\tprint_name(buffer, name);\n+\tif (value == 0)\n+\t\tstrcpy(buffer + NAME_SPACE, \"Success\");\n+\telse if (value == 1)\n+\t\tstrcpy(buffer + NAME_SPACE, \"Insufficent data\");\n+\telse if (value == 2)\n+\t\tstrcpy(buffer + NAME_SPACE, \"Insufficent space\");\n+\telse if (value == 3)\n+\t\tstrcpy(buffer + NAME_SPACE, \"Not attempted\");\n+\telse\n+\t\tsprintf(buffer + NAME_SPACE, \"Unknown code %u\", value);\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void print_fd(FILE *filp, const char *name, int fd)\n+{\n+\tchar buffer[NAME_SPACE + 40];\n+\tprint_name(buffer, name);\n+\tif (fd >= 0)\n+\t\tsprintf(buffer + NAME_SPACE, \"%i\", fd);\n+\telse\n+\t\tstrcpy(buffer + NAME_SPACE, \"Not connected\");\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void print_delayed_failure(FILE *filp, const char *name, unsigned dftype,\n+\tunsigned dfailure)\n+{\n+\tchar buffer[NAME_SPACE + 40];\n+\tprint_name(buffer, name);\n+\tif (dftype == 0)\n+\t\tstrcpy(buffer + NAME_SPACE, \"<None>\");\n+\telse if (dftype == 1)\n+\t\tsprintf(buffer + NAME_SPACE, \"Code %u while handshaking\", dfailure);\n+\telse if (dftype == 2)\n+\t\tsprintf(buffer + NAME_SPACE, \"Code %u while transferring\", dfailure);\n+\telse\n+\t\tsprintf(buffer + NAME_SPACE, \"Code %u while in unknown %u\", dfailure, dftype);\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void print_format(FILE *filp, const char *name, const char *format, ...)\n+{\n+\tva_list args;\n+\tchar buffer[NAME_SPACE + 120];\n+\tprint_name(buffer, name);\n+\tva_start(args, format);\n+\tvsprintf(buffer + NAME_SPACE, format, args);\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void print_since(FILE *filp, const char *name, struct timeval event,\n+\tstruct timeval now)\n+{\n+\tchar buffer[NAME_SPACE + 40];\n+\tuint64_t etime;\n+\tuint64_t ntime;\n+\tuint64_t tdelta;\n+\tprint_name(buffer, name);\n+\tetime = (uint64_t)event.tv_sec * 1000000 + event.tv_usec;\n+\tntime = (uint64_t)now.tv_sec * 1000000 + now.tv_usec;\n+\tif (etime < ntime)\n+\t\ttdelta = ntime - etime;\n+\telse\n+\t\ttdelta = 0;\n+\tif (tdelta < 1000000) {\n+\t\tsprintf(buffer + NAME_SPACE, \"<1s ago\");\n+\t} else {\n+\t\tsprintf(buffer + NAME_SPACE, \"%llu.%03us ago\",\n+\t\t\t(unsigned long long)(tdelta / 1000000),\n+\t\t\t(unsigned)(tdelta % 1000000 / 1000));\n+\t}\n+\tfprintf(filp, \"%s\\n\", buffer);\n+}\n+\n+void user_debug(struct user *user, FILE* filp)\n+{\n+\tstruct timeval tv;\n+\tif (!user) {\n+\t\tfprintf(filp, \"\\n\\n\\nNo transport present\\n\\n\\n\\n\");\n+\t\treturn;\n+\t}\n+\tgettimeofday(&tv, NULL);\n+\n+\tfprintf(filp, \"\\n\\n\\nTransport level debugging output (version 3):\\n\");\n+\tprint_boolean(filp, \"Socket raw EOF received flag:\", user->u_black_in_eof);\n+\tprint_boolean(filp, \"Socket raw EOF sent flag:\", user->u_black_out_eof);\n+\tprint_boolean(filp, \"Encrypted EOF received flag:\", user->u_black_in_d_eof);\n+\tprint_boolean(filp, \"Encrypted EOF sent flag:\", user->u_black_out_d_eof);\n+\tprint_boolean(filp, \"Assume more local inptt flag:\", user->u_red_assume_more);\n+\tprint_boolean(filp, \"TLS handshaked flag:\", user->u_tls_active);\n+\tprint_boolean(filp, \"Received local input flag:\", user->u_red_in_have_data);\n+\tprint_status(filp, \"Last read status:\", user->u_want_read);\n+\tprint_status(filp, \"Last write status:\", user->u_want_write);\n+\tprint_status(filp, \"Last handshake status:\", user->u_want_hand);\n+\tprint_format(filp, \"Error header bytes sent:\", \"%u\", user->u_red_err_hdr_sent);\n+\tprint_delayed_failure(filp, \"Delayed TLS failure:\", user->u_delay_tls_failure,\n+\t\tuser->u_delay_alert);\n+\tprint_boolean(filp, \"Seen input data flag:\", user->u_seen_input_data);\n+\tprint_fd(filp, \"Socket fd:\", user->u_black_fd);\n+\tprint_fd(filp, \"Local input fd:\", user->u_red_in_fd);\n+\tprint_fd(filp, \"Local output fd:\", user->u_red_out_fd);\n+\tprint_fd(filp, \"Local error fd:\", user->u_red_err_fd);\n+\tif (user->u_deadline.tv_sec < 0)\n+\t\tprint_format(filp, \"Deadline:\", \"None set\");\n+\telse\n+\t\tprint_format(filp, \"Deadline:\", \"%lu.%06lu\\n\",\n+\t\t\t(unsigned long)user->u_deadline.tv_sec,\n+\t\t\t(unsigned long)user->u_deadline.tv_usec);\n+\tprint_format(filp, \"Failure code:\", \"%i\", user->u_failure);\n+\tif (user->u_errmsg)\n+\t\tprint_format(filp, \"Detailed error:\", \"%s\", user->u_errmsg);\n+\telse\n+\t\tprint_format(filp, \"Detailed error:\", \"<Not set>\");\n+\tprint_format(filp, \"Delayed failure code:\", \"%i\", user->u_delay_failure);\n+\tif (user->u_tls_session)\n+\t\tprint_format(filp, \"TLS context:\", \"Present\");\n+\telse\n+\t\tprint_format(filp, \"TLS context:\", \"Absent\");\n+\tprint_format(filp, \"Socket input buffer:\", \"Used: %lu, free: %lu\",\n+\t\t(unsigned long)cbuffer_used(user->u_black_in_buf),\n+\t\t(unsigned long)cbuffer_free(user->u_black_in_buf));\n+\tprint_format(filp, \"Socket output buffer:\", \"Used: %lu, free: %lu\",\n+\t\t(unsigned long)cbuffer_used(user->u_black_out_buf),\n+\t\t(unsigned long)cbuffer_free(user->u_black_out_buf));\n+\tprint_format(filp, \"Local input buffer:\", \"Used: %lu, free: %lu\",\n+\t\t(unsigned long)cbuffer_used(user->u_red_in_buf),\n+\t\t(unsigned long)cbuffer_free(user->u_red_in_buf));\n+\tprint_format(filp, \"Local output buffer:\", \"Used: %lu, free: %lu\",\n+\t\t(unsigned long)cbuffer_used(user->u_red_out_buf),\n+\t\t(unsigned long)cbuffer_free(user->u_red_out_buf));\n+\tprint_format(filp, \"Local error buffer:\", \"Used: %lu, free: %lu\",\n+\t\t(unsigned long)cbuffer_used(user->u_red_err_buf),\n+\t\t(unsigned long)cbuffer_free(user->u_red_err_buf));\n+\tprint_format(filp, \"TX bytes:\", \"Raw: %llu, Decoded: %llu\",\n+\t\tuser->u_raw_tx, user->u_decoded_tx);\n+\tprint_format(filp, \"RX bytes:\", \"Raw: %llu, Decoded: %llu\",\n+\t\tuser->u_raw_rx, user->u_decoded_rx);\n+\tprint_since(filp, \"Last TX:\", user->u_last_tx, tv);\n+\tprint_since(filp, \"Last RX:\", user->u_last_rx, tv);\n+\tfprintf(filp,\"\\n\\n\\n\");\n+}\ndiff --git a/git-over-tls/user.h b/git-over-tls/user.h\nnew file mode 100644\nindex 0000000..ac0d1fa\n--- /dev/null\n+++ b/git-over-tls/user.h\n@@ -0,0 +1,367 @@\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 <stdio.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. */\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+/*\n+ * Print debugging output.\n+ *\n+ * Input:\n+ *\tuser\t\tThe user session to interrogate.\n+ *\tfilp\t\tWhere to write the output.\n+ */\n+void user_debug(struct user *user, FILE *filp);\n+\n+#ifdef __cplusplus\n+}\n+#endif\n+\n+#endif\n-- \n1.7.1.rc2.10.g714149\n"},{"id":"140687","messageId":"1272733791-11341-3-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"23654","inReplyTo":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[PATCH 2/3] git over TLS (gits://) support (part 2)","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-05-01T17:09:50Z","receivedAt":"2010-05-01T17:09:50Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n git-over-tls/.gitignore                      |    5 +\n git-over-tls/base64.c                        |  171 +++++++\n git-over-tls/base64.h                        |   21 +\n git-over-tls/certificate.c                   |  306 ++++++++++++\n git-over-tls/certificate.h                   |   28 +\n git-over-tls/connect.c                       |  275 +++++++++++\n git-over-tls/connect.h                       |   17 +\n git-over-tls/genkeypair.c                    |   38 ++\n git-over-tls/gensrpverifier.c                |  377 ++++++++++++++\n git-over-tls/getkeyid.c                      |  179 +++++++\n git-over-tls/gits-send-special-command       |   22 +\n git-over-tls/gits-send-special-command-nourl |   23 +\n git-over-tls/home.c                          |  229 +++++++++\n git-over-tls/home.h                          |   71 +++\n git-over-tls/hostkey.c                       |   81 +++\n git-over-tls/hostkey.h                       |   15 +\n git-over-tls/keypairs.c                      |   60 +++\n git-over-tls/keypairs.h                      |   16 +\n git-over-tls/main.c                          |  684 ++++++++++++++++++++++++++\n git-over-tls/misc.c                          |   15 +\n git-over-tls/misc.h                          |   27 +\n git-over-tls/mkcert.c                        |  474 ++++++++++++++++++\n git-over-tls/prompt.c                        |  100 ++++\n git-over-tls/prompt.h                        |   18 +\n git-over-tls/srp_askpass.c                   |  110 ++++\n git-over-tls/srp_askpass.h                   |   16 +\n 26 files changed, 3378 insertions(+), 0 deletions(-)\n create mode 100644 git-over-tls/.gitignore\n create mode 100644 git-over-tls/base64.c\n create mode 100644 git-over-tls/base64.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 100755 git-over-tls/gits-send-special-command-nourl\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/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\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/base64.c b/git-over-tls/base64.c\nnew file mode 100644\nindex 0000000..dbf5334\n--- /dev/null\n+++ b/git-over-tls/base64.c\n@@ -0,0 +1,171 @@\n+#include \"base64.h\"\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+void encode_uint32(unsigned char *ptr, unsigned long value)\n+{\n+\tptr[0] = (unsigned char)(value >> 24);\n+\tptr[1] = (unsigned char)(value >> 16);\n+\tptr[2] = (unsigned char)(value >> 8);\n+\tptr[3] = (unsigned char)(value);\n+}\n+\n+/* Base64 character value */\n+static int char_value(unsigned char ch)\n+{\n+\tif (ch >= 'A' && ch <= 'Z')\n+\t\treturn ch - 'A';\n+\tif (ch >= 'a' && ch <= 'z')\n+\t\treturn ch - 'a' + 26;\n+\tif (ch >= '0' && ch <= '9')\n+\t\treturn ch - '0' + 52;\n+\tif (ch == '+')\n+\t\treturn 62;\n+\tif (ch == '/')\n+\t\treturn 63;\n+\treturn -1;\n+}\n+\n+/* Decode Base64 chunk. */\n+unsigned char *decode_base64_chunk(const unsigned char *chunk,\n+\tsize_t chunk_len, size_t *key_len)\n+{\n+\tunsigned char *ret;\n+\tunsigned blockmod = 0;\n+\tint buf[4];\n+\tsize_t i;\n+\n+\tret = xmalloc((chunk_len + 3) / 4 * 3);\n+\t*key_len = 0;\n+\n+\tfor(i = 0; i < chunk_len; i++) {\n+\t\tbuf[blockmod] = char_value(chunk[i]);\n+\t\tif (buf[blockmod] >= 0)\n+\t\t\tblockmod++;\n+\t\telse\n+\t\t\tbuf[blockmod] = 0;\n+\t\tif(blockmod == 4) {\n+\t\t\tint v = (buf[0] << 18) | (buf[1] << 12) |\n+\t\t\t\t(buf[2] << 6) | buf[3];\n+\t\t\tret[(*key_len)++] = (unsigned char)(v >> 16);\n+\t\t\tret[(*key_len)++] = (unsigned char)(v >> 8);\n+\t\t\tret[(*key_len)++] = (unsigned char)(v);\n+\t\t\tblockmod = 0;\n+\t\t\tbuf[0] = buf[1] = buf[2] = buf[3] = 0;\n+\t\t}\n+\t}\n+\tif(blockmod > 0) {\n+\t\tint v = (buf[0] << 18) | (buf[1] << 12) |\n+\t\t\t(buf[2] << 6) | buf[3];\n+\t\tif (blockmod > 1)\n+\t\t\tret[(*key_len)++] = (unsigned char)(v >> 16);\n+\t\tif (blockmod > 2)\n+\t\t\tret[(*key_len)++] = (unsigned char)(v >> 8);\n+\t}\n+\treturn ret;\n+}\n+\n+/* Return address of next (nonempty) line, or NULL if none. */\n+const unsigned char *next_line(const unsigned char *blob,\n+\tsize_t *remaining)\n+{\n+\twhile (*blob != '\\r' && *blob != '\\n') {\n+\t\tblob++;\n+\t\t(*remaining)--;\n+\t\tif (!*remaining)\n+\t\t\treturn NULL;\n+\t}\n+\twhile (*blob == '\\r' || *blob == '\\n') {\n+\t\tblob++;\n+\t\t(*remaining)--;\n+\t\tif (!*remaining)\n+\t\t\treturn NULL;\n+\t}\n+\treturn blob;\n+}\n+\n+#define STATUS_INIT 0\n+#define STATUS_HEADER 1\n+#define STATUS_CONTINUE 2\n+#define STATUS_EOL 3\n+#define STATUS_EOL_CONTINUE 4\n+\n+/* Find start of base64 blob. */\n+const unsigned char *base64_blob_start(const unsigned char *blob,\n+\tsize_t *remaining)\n+{\n+\tint status = STATUS_INIT;\n+\tconst unsigned char *line_start;\n+\tsize_t size_start;\n+\n+\tline_start = blob;\n+\tsize_start = *remaining;\n+\n+\twhile(1) {\n+\t\tswitch(status) {\n+\t\tcase STATUS_INIT:\n+\t\t\tif (!*remaining || *blob == '\\r' || *blob == '\\n') {\n+\t\t\t\t/* Back off to start of line. */\n+\t\t\t\tblob = line_start;\n+\t\t\t\t*remaining = size_start;\n+\t\t\t\treturn blob;\n+\t\t\t}\n+\t\t\tif (*blob == ':')\n+\t\t\t\tstatus = STATUS_HEADER;\n+\t\t\tbreak;\n+\t\tcase STATUS_HEADER:\n+\t\t\tif (!*remaining) {\n+\t\t\t\t*remaining = 0;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tif (*blob == '\\r' || *blob == '\\n')\n+\t\t\t\tstatus = STATUS_EOL;\n+\t\t\tif (*blob == '\\\\')\n+\t\t\t\tstatus = STATUS_CONTINUE;\n+\t\t\tbreak;\n+\t\tcase STATUS_CONTINUE:\n+\t\t\tif (!*remaining) {\n+\t\t\t\t*remaining = 0;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tif (*blob == '\\r' || *blob == '\\n')\n+\t\t\t\tstatus = STATUS_EOL_CONTINUE;\n+\t\t\telse\n+\t\t\t\tstatus = STATUS_HEADER;\n+\t\t\tbreak;\n+\t\tcase STATUS_EOL:\n+\t\t\tif (!*remaining) {\n+\t\t\t\t*remaining = 0;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tif (*blob != '\\r' && *blob != '\\n') {\n+\t\t\t\t/* Mark line start and back off by one. */\n+\t\t\t\tline_start = blob;\n+\t\t\t\tsize_start = *remaining;\n+\t\t\t\tblob--;\n+\t\t\t\t(*remaining)++;\n+\t\t\t\tstatus = STATUS_INIT;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase STATUS_EOL_CONTINUE:\n+\t\t\tif (!*remaining) {\n+\t\t\t\t*remaining = 0;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tif (*blob != '\\r' && *blob != '\\n') {\n+\t\t\t\t/* Mark line start and back off by one. */\n+\t\t\t\tline_start = blob;\n+\t\t\t\tsize_start = *remaining;\n+\t\t\t\tblob--;\n+\t\t\t\t(*remaining)++;\n+\t\t\t\tstatus = STATUS_HEADER;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\t\tblob++;\n+\t\t(*remaining)--;\n+\t}\n+}\ndiff --git a/git-over-tls/base64.h b/git-over-tls/base64.h\nnew file mode 100644\nindex 0000000..b4352b1\n--- /dev/null\n+++ b/git-over-tls/base64.h\n@@ -0,0 +1,21 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 _base64__h__included__\n+#define _base64__h__included__\n+\n+#include <stdlib.h>\n+\n+unsigned char *decode_base64_chunk(const unsigned char *chunk,\n+\tsize_t chunk_len, size_t *key_len);\n+const unsigned char *base64_blob_start(const unsigned char *blob,\n+\tsize_t *remaining);\n+const unsigned char *next_line(const unsigned char *blob,\n+\tsize_t *remaining);\n+void encode_uint32(unsigned char *ptr, unsigned long value);\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..f16c7b6\n--- /dev/null\n+++ b/git-over-tls/connect.c\n@@ -0,0 +1,275 @@\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+#include <arpa/inet.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+#define BUFFERLEN 256\n+\n+extern int verbose;\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+\tif (verbose) {\n+\t\tfprintf(stderr, \"Connecting to unix socket '%s'...\",\n+\t\t\tpath);\n+\t\tfflush(stderr);\n+\t}\n+\tret = connect(fd, (struct sockaddr*)&saddru, plen);\n+\tif (ret < 0) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"%s\\n\", strerror(errno));\n+\t\tdie_errno(\"Can't connect to '%s'\", path);\n+\t}\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Success.\\n\");\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, const char *port,\n+\tint protocol, struct sockaddr *addr, int size)\n+{\n+\tchar address[1024];\n+\tchar nport[256];\n+\tint fd, ret;\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+\n+#ifndef NO_IPV6\n+\tint r = getnameinfo(addr, size, address, 1024, nport, 256,\n+\t\tNI_NUMERICSERV | NI_NUMERICHOST);\n+\tif (r)\n+\t\tstrcpy(address, \"<parse error>\");\n+#else\n+\tstrcpy(address, inet_ntoa(((struct sockaddr_in*)addr)->sin_addr));\n+\tsprintf(nport, \"%u\", (unsigned)ntohs(((struct sockaddr_in*)addr)->\n+\t\tsin_port));\n+#endif\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Connecting to address '%s', port %s...\",\n+\t\t\taddress, nport);\n+\t\tfflush(stderr);\n+\t}\n+\tret = connect(fd, addr, size);\n+\tif (ret < 0) {\n+\t\tfprintf(stderr, \"%s\\n\", strerror(errno));\n+\t\treturn -1;\n+\t}\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Success\\n\");\n+\treturn fd;\n+}\n+\n+int connect_gethostbyname(const char *_host,\n+\tconst char *port, const char *_protocol, const char *_family)\n+{\n+\tint protocol = 0;\n+\tint family = 0;\n+\tstruct protoent* proto;\n+\n+\tif (!_family)\n+\t\tfamily = 0;\n+\telse if (!strcmp(_family, \"ipv4\"))\n+\t\tfamily = AF_INET;\n+#ifndef NO_IPV6\n+\telse if (!strcmp(_family, \"ipv6\"))\n+\t\tfamily = AF_INET6;\n+#endif\n+\telse\n+\t\tdie(\"Unknown address type '%s'\", _family);\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Resolving protocol '%s'...\", _protocol);\n+\t\tfflush(stderr);\n+\t}\n+\n+\tproto = getprotobyname(_protocol);\n+\tif (!proto) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Failed\\n\");\n+\t\tdie(\"Unknown protocol '%s'\", _protocol);\n+\t}\n+\tprotocol = proto->p_proto;\n+\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Protocol #%i\\n\", protocol);\n+\n+#ifndef NO_IPV6\n+\tstruct addrinfo hints;\n+\tstruct addrinfo *returned;\n+\tint fd, ret;\n+\n+\tmemset(&hints, 0, sizeof(hints));\n+\thints.ai_socktype = SOCK_STREAM;\n+\thints.ai_family = family;\n+\thints.ai_protocol = protocol;\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Resolving host '%s', port '%s'...\",\n+\t\t\t_host, port);\n+\t\tfflush(stderr);\n+\t}\n+\n+\tret = getaddrinfo(_host, port, &hints, &returned);\n+\tif (ret) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"%s\\n\", gai_strerror(ret));\n+\t\tdie(\"Can't resolve address: %s\", gai_strerror(ret));\n+\t}\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Success\\n\");\n+\t}\n+\n+\twhile (returned) {\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+\tint _port;\n+\tunsigned long _port2;\n+\tstruct servent* serv;\n+\tchar* end;\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Resolving service '%s/%s'...\", port,\n+\t\t\t_protocol);\n+\t\tfflush(stderr);\n+\t}\n+\n+\t_port2 = strtoul(port, &end, 10);\n+\tserv = getservbyname(port, _protocol);\n+\tif (!serv && (_port2 < 1 || _port2 > 65535 || *end)) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Not found\\n\");\n+\t\tdie(\"No such port or service '%s/%s'\", port,\n+\t\t\t_protocol);\n+\t} else if (serv)\n+\t\t_port = serv->s_port;\n+\telse\n+\t\t_port = (int)_port2;\n+\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Port %u\\n\", (unsigned)_port);\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Resolving host '%s'...\", _host);\n+\t\tfflush(stderr);\n+\t}\n+\n+\thost = gethostbyname(_host);\n+\tif (!host || !host->h_addr) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Failed\\n\");\n+\t\tdie(\"Can't find host\");\n+\t}\n+\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Success\\n\");\n+\n+\tif (host->h_addrtype != AF_INET)\n+\t\tdie(\"Can't handle address type in result\");\n+\n+next_address:\n+\tmemset(&saddr4, 0, sizeof(saddr4));\n+\tsaddr4.sin_family = AF_INET;\n+\tmemcpy(&saddr4.sin_addr, host->h_addr_list[0], 4);\n+\tsaddr4.sin_port = htons(_port);\n+\tfd = connect_address(_host, port, 0,\n+\t\t(struct sockaddr*)&saddr4,\n+\t\tsizeof(struct sockaddr_in));\n+\n+\tif (fd >= 0)\n+\t\tgoto out;\n+\n+\thost->h_addr_list++;\n+\tif (host->h_addr_list[0])\n+\t\tgoto next_address;\n+\n+\tdie(\"Can't connect to host %s\", _host);\n+out:\n+\treturn fd;\n+#endif\n+}\n+\n+int connect_host(const char *_host, const char *port, const char *tproto,\n+\tconst char *addrspace)\n+{\n+\tchar *hostcopy;\n+\n+\tif (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/') ||\n+\t\t(addrspace && !strcmp(addrspace, \"unix\")))\n+\t\treturn connect_unix(_host);\n+\n+\thostcopy = xmalloc(strlen(_host) + 1);\n+\tstrcpy(hostcopy, _host);\n+\n+\treturn connect_gethostbyname(hostcopy, port, tproto, addrspace);\n+}\ndiff --git a/git-over-tls/connect.h b/git-over-tls/connect.h\nnew file mode 100644\nindex 0000000..bc861be\n--- /dev/null\n+++ b/git-over-tls/connect.h\n@@ -0,0 +1,17 @@\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+#include <stdlib.h>\n+\n+//Returns connected fd or dies.\n+int connect_host(const char *host, const char *port,\n+\tconst char *tproto, const char *addrspace);\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..3d1decb\n--- /dev/null\n+++ b/git-over-tls/gensrpverifier.c\n@@ -0,0 +1,377 @@\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+#include <string.h>\n+#include \"home.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\tint fd;\n+\t\tfd = open_create_dirs(file, O_CREAT | O_EXCL | O_WRONLY, 0600);\n+\t\tif (fd < 0) {\n+\t\t\tfprintf(stderr, \"Can't open '%s': %s\\n\", file,\n+\t\t\t\tstrerror(errno));\n+\t\t\tgoto file_again;\n+\t\t}\n+\t\tfilp = xfdopen(fd, \"w\");\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..baf394f\n--- /dev/null\n+++ b/git-over-tls/getkeyid.c\n@@ -0,0 +1,179 @@\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 \"ssh.h\"\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <limits.h>\n+#include <string.h>\n+#include <gnutls/openpgp.h>\n+#include <gcrypt.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(\"--ssh\\n\");\n+\tprintf(\"\\tRead SSH keys\\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+#define MAXKEY 65536\n+#define HASHBUFFERSIZE 28\n+#define HASHFUNCTION GCRY_MD_SHA224\n+\n+void dump_blob(const char *name, const unsigned char *x, size_t i);\n+\n+int do_ssh_key(const char *name)\n+{\n+\tFILE* filp;\n+\tint s;\n+\tchar filename[PATH_MAX + 1];\n+\tunsigned char key[MAXKEY];\n+\tsize_t keysize;\n+\tchar *path;\n+\tunsigned char *x;\n+\tsize_t i;\n+\tunsigned char hbuffer[HASHBUFFERSIZE];\n+\n+\tif (strchr(name, '/'))\n+\t\ts = snprintf(filename, PATH_MAX + 1, \"%s\", name);\n+\telse\n+\t\ts = snprintf(filename, PATH_MAX + 1, \"~/.ssh/%s.pub\",\n+\t\t\tname);\n+\tif (s < 0 || s > PATH_MAX)\n+\t\tdie(\"Insanely long keyname\");\n+\n+\tpath = expand_path(filename);\n+\n+\t/* Read the pubkey file. */\n+\tfilp = fopen(path, \"r\");\n+\tif (!filp)\n+\t\tdie(\"Can't open '%s'\", path);\n+\tkeysize = fread(key, 1, MAXKEY, filp);\n+\tif (ferror(filp))\n+\t\tdie(\"Can't read '%s'\", path);\n+\tif (!feof(filp))\n+\t\tdie(\"Keyfile '%s' too large\", path);\n+\tfclose(filp);\n+\n+\tx = extract_key_from_file(key, keysize, &i);\n+\n+\tgcry_md_hash_buffer(HASHFUNCTION, hbuffer, x, i);\n+\n+\tprintf(\"ssh-\");\n+\tfor (s = 0; s < HASHBUFFERSIZE; s++)\n+\t\tprintf(\"%02x\", hbuffer[s]);\n+\tprintf(\"\\n\");\n+\treturn 0;\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+\tchar *filename2;\n+\tunsigned char key[KEYBUF];\n+\tint s;\n+\tunsigned vout;\n+\tsize_t vout2;\n+\n+\tif (argc < 2 || argc > 3) {\n+\t\tfprintf(stderr, \"syntax: %s <keyname>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s <keyfile>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s --ssh <keyname>\\n\", argv[0]);\n+\t\tfprintf(stderr, \"syntax: %s --ssh <keyfile>\\n\", argv[0]);\n+\t\treturn 1;\n+\t}\n+\n+\tif (!strcmp(argv[1], \"--help\"))\n+\t\tdo_help();\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+\tif (!strcmp(argv[1], \"--ssh\"))\n+\t\treturn do_ssh_key(argv[2]);\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, \"$XDG_CONFIG_HOME/.gits/keys/%s\",\n+\t\t\targv[1]);\n+\tif (s < 0 || s > PATH_MAX)\n+\t\tdie(\"Insanely long keyname\");\n+\n+\n+\tfilename2 = expand_path(filename);\n+\tcertificate = parse_certificate(filename2, &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+\tvout2 = KEYBUF;\n+\ts = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout2);\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)vout2; 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/gits-send-special-command-nourl b/git-over-tls/gits-send-special-command-nourl\nnew file mode 100755\nindex 0000000..e9de10a\n--- /dev/null\n+++ b/git-over-tls/gits-send-special-command-nourl\n@@ -0,0 +1,23 @@\n+#!/bin/bash\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-nourl: Send special command to \"\n+\techo \"server with no explicit path\"\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> the \"\n+\techo -e \"\\x09path part of URL is ignored.\"\n+\texit 0\n+fi\n+\n+git-remote-gits --nourl-service=$1 $2\ndiff --git a/git-over-tls/home.c b/git-over-tls/home.c\nnew file mode 100644\nindex 0000000..1d2b954\n--- /dev/null\n+++ b/git-over-tls/home.c\n@@ -0,0 +1,229 @@\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 \"home.h\"\n+#include <string.h>\n+#include <stdlib.h>\n+#include <pwd.h>\n+#include <unistd.h>\n+#include <sys/stat.h>\n+#include <errno.h>\n+#include <fcntl.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+static 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+}\n+\n+static char *replace_variable(char *path, const char *value)\n+{\n+\tchar *slash;\n+\tchar *oldpath = path;\n+\n+\tslash = strchr(path, '/');\n+\tif (slash) {\n+\t\tsize_t len;\n+\t\tlen = strlen(path) - (slash - path) +\n+\t\t\tstrlen(value);\n+\t\tpath = xmalloc(len + 1);\n+\t\tif (value[strlen(value) - 1] == '/')\n+\t\t\tsprintf(path, \"%s%s\", value, slash + 1);\n+\t\telse\n+\t\t\tsprintf(path, \"%s%s\", value, slash);\n+\t} else {\n+\t\tsize_t len;\n+\t\tlen = strlen(value);\n+\t\tpath = xstrdup(value);\n+\t\tif (path[len - 1] == '/')\n+\t\t\tpath[len - 1] = '\\0';\n+\t}\n+\tfree(oldpath);\n+\treturn path;\n+}\n+\n+int check_component(const char *path, size_t len)\n+{\n+\tstruct stat s;\n+\tint r;\n+\tchar *tmp;\n+\ttmp = xstrndup(path, len);\n+\n+\tr = stat(tmp, &s);\n+\tif (r < 0 && errno != ENOENT) {\n+\t\terror(\"Creating '%s', unable to stat '%s': %s\",\n+\t\t\tpath, tmp, strerror(errno));\n+\t\treturn -1;\n+\t} else if (r < 0) {\n+\t\t/* Attempt to create. */\n+\t\tint r;\n+\t\tr = mkdir(tmp, 0700);\n+\t\tif (r >= 0)\n+\t\t\tgoto end;\n+\t\terror(\"Creating '%s', unable to create '%s': %s\",\n+\t\t\tpath, tmp, strerror(errno));\n+\t\treturn -1;\n+\t} else if (S_ISDIR(s.st_mode)) {\n+\t\t/* Exists and directory, OK. */\n+\t} else {\n+\t\terror(\"Creating '%s', '%s' exists but is not a directory\",\n+\t\t\tpath, tmp);\n+\t\treturn -1;\n+\t}\n+end:\n+\tfree(tmp);\n+\treturn 0;\n+}\n+\n+static int ensure_directory_real(const char *path)\n+{\n+\tsize_t index;\n+\tsize_t last_index = 0;\n+\n+\t/* CWD always exists. */\n+\tif (!*path)\n+\t\treturn 0;\n+\n+\tfor (index = 0; path[index]; index++) {\n+\t\t/* In middle of name? */\n+\t\tif (path[index] != '/')\n+\t\t\tcontinue;\n+\t\t/* Root always exists. */\n+\t\tif (index == 0)\n+\t\t\tcontinue;\n+\t\tif (check_component(path, index) < 0)\n+\t\t\treturn -1;\n+\t\tlast_index = index;\n+\t}\n+\tif (index > last_index + 1)\n+\t\tif (check_component(path, index) < 0)\n+\t\t\treturn -1;\n+\treturn 0;\n+}\n+\n+char *expand_path(const char *path)\n+{\n+\tchar *path2;\n+\tpath2 = xstrdup(path);\n+\n+\t/*\n+\t * Handle $XDG_CONFIG_HOME and $XDG_DATA_HOME first, as\n+\t * these may use $HOME.\n+\t */\n+\tif (!strcmp(path2, \"$XDG_CONFIG_HOME\") ||\n+\t\t!strncmp(path2, \"$XDG_CONFIG_HOME/\", 17)) {\n+\t\tconst char* var;\n+\n+\t\tvar = getenv(\"XDG_CONFIG_HOME\");\n+\t\tif (!var || !*var)\n+\t\t\tvar = \"$HOME/.config/\";\n+\t\tpath2 = replace_variable(path2, var);\n+\t}\n+\tif (!strcmp(path2, \"$XDG_DATA_HOME\") ||\n+\t\t!strncmp(path2, \"$XDG_DATA_HOME/\", 15)) {\n+\t\tconst char* var;\n+\n+\t\tvar = getenv(\"XDG_DATA_HOME\");\n+\t\tif (!var || !*var)\n+\t\t\tvar = \"$HOME/.local/share\";\n+\t\tpath2 = replace_variable(path2, var);\n+\t}\n+\tif (!strcmp(path2, \"$HOME\") ||\n+\t\t!strncmp(path2, \"$HOME/\", 6)) {\n+\t\tconst char* var;\n+\n+\t\tvar = get_home();\n+\t\tpath2 = replace_variable(path2, var);\n+\t}\n+\tif (!strcmp(path2, \"~\") ||\n+\t\t!strncmp(path2, \"~/\", 2)) {\n+\t\tconst char* var;\n+\n+\t\tvar = get_home();\n+\t\tpath2 = replace_variable(path2, var);\n+\t}\n+\treturn path2;\n+}\n+\n+int ensure_directory(const char *path)\n+{\n+\tchar *path2;\n+\tint r;\n+\n+\tpath2 = expand_path(path);\n+\tr = ensure_directory_real(path2);\n+\tfree(path2);\n+\treturn r;\n+}\n+\n+int open_create_dirs(const char *path, int flags, mode_t mode)\n+{\n+\tint r;\n+\tstruct stat s;\n+\tchar *path2;\n+\tchar *path3 = NULL;\n+\tchar *slash;\n+\n+\tpath2 = expand_path(path);\n+\n+\tr = stat(path2, &s);\n+\tif (r < 0 && errno != ENOENT) {\n+\t\terror(\"Can't open/create '%s': stat failed: %s\",\n+\t\t\tpath2, strerror(errno));\n+\t\treturn -1;\n+\t} else if (r < 0 && (flags & O_CREAT) == 0) {\n+\t\terrno = ENOENT;\n+\t\treturn -1;\n+\t} else if (r >= 0 && !S_ISREG(s.st_mode)) {\n+\t\terror(\"Can't open/create '%s': It isn't regular file\", path2);\n+\t\terrno = EEXIST;\n+\t\treturn -1;\n+\t}\n+\n+\tslash = strrchr(path2, '/');\n+\tif(slash) {\n+\t\tpath3 = xstrndup(path2, slash - path2);\n+\t\tif (ensure_directory_real(path3) < 0)\n+\t\t\treturn -1;\n+\t}\n+\n+\tr = open(path2, flags, mode);\n+\n+\tfree(path2);\n+\tfree(path3);\n+\n+\treturn r;\n+}\ndiff --git a/git-over-tls/home.h b/git-over-tls/home.h\nnew file mode 100644\nindex 0000000..842cd8c\n--- /dev/null\n+++ b/git-over-tls/home.h\n@@ -0,0 +1,71 @@\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 _home__h__included__\n+#define _home__h__included__\n+\n+#include <sys/types.h>\n+\n+/*\n+ * Ensure that directory specified exists. If it does not, attempt\n+ * to create it.\n+ *\n+ *Input:\n+ *\tpath\t\tThe directory to ensure.\n+ *\n+ *Output:\n+ *\tReturn value\t0 on success, -1 on failure.\n+ *\n+ *Notes:\n+ *\t- Path starting with $HOME is special, it is\n+ *\t  relative to user home directory.\n+ *\t- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME\n+ *\t  are special. Those are interpretted as relative to XDG\n+ *\t  base dir spec directories.\n+ */\n+int ensure_directory(const char *path);\n+\n+/*\n+ * Open the specified regular file. Leading path components are\n+ * automatically created if file needs to be created.\n+ *\n+ *Input:\n+ *\tpath\t\tThe file to open.\n+ *\tflags\t\tFlags to pass to open.\n+ *\tmode\t\tMode to pass to open.\n+ *\n+ *Output:\n+ *\tReturn value\tFile descriptor. -1 on failure.\n+ *\n+ *Notes:\n+ *\t- Path starting with $HOME is special, it is\n+ *\t  relative to user home directory.\n+ *\t- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME\n+ *\t  are special. Those are interpretted as relative to XDG\n+ *\t  base dir spec directories.\n+ */\n+int open_create_dirs(const char *path, int flags, mode_t mode);\n+\n+/*\n+ * Expand the specified path.\n+ *\n+ *Input:\n+ *\tpath\t\tThe path to expand.\n+ *\n+ *Output:\n+ *\tReturn value\tMallocced copy of expanded path.\n+ *\n+ *Notes:\n+ *\t- Path starting with $HOME is special, it is\n+ *\t  relative to user home directory.\n+ *\t- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME\n+ *\t  are special. Those are interpretted as relative to XDG\n+ *\t  base dir spec directories.\n+ */\n+char *expand_path(const char *path);\n+\n+#endif\ndiff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c\nnew file mode 100644\nindex 0000000..715bd8a\n--- /dev/null\n+++ b/git-over-tls/hostkey.c\n@@ -0,0 +1,81 @@\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 129\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+\tsize_t vout2;\n+\tunsigned char key[KEYBUF] = {0};\n+\tchar keydec[2 * KEYBUF + 1] = {0};\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+\tvout2 = KEYBUF;\n+\ts = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout2);\n+\tif (s < 0)\n+\t\tdie(\"Server sent bad hostkey: %s\", gnutls_strerror(s));\n+\n+\tgnutls_openpgp_crt_deinit(cert);\n+\n+\t/*\n+\t * Be nice to users and strip this in case it gets\n+\t * retained from key id calculator.\n+\t */\n+\tif (!strncmp(hostname, \"openpgp-\", 8))\n+\t\thostname += 8;\n+\n+\n+\tfor (s = 0; s < vout2; s++)\n+\t\tsprintf(keydec + 2 * s, \"%02x\", (int)key[s]);\n+\tif (strcmp(hostname, keydec))\n+\t\tgoto mismatch;\n+\treturn;\n+mismatch:\n+\tdie(\"HOST KEY MISMATCH FOR HOST ('%s' vs '%s')!\", hostname, keydec);\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/keypairs.c b/git-over-tls/keypairs.c\nnew file mode 100644\nindex 0000000..20f94dc\n--- /dev/null\n+++ b/git-over-tls/keypairs.c\n@@ -0,0 +1,60 @@\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 \"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+\tchar *keypath2;\n+\tstruct certificate cert;\n+\tint r;\n+\n+\tr = snprintf(keypath, PATH_MAX + 1, \"$XDG_CONFIG_HOME/gits/keys/%s\",\n+\t\tusername);\n+\tif (r < 0 || r > PATH_MAX) {\n+\t\tdie(\"Username too long\");\n+\t}\n+\n+\tkeypath2 = expand_path(keypath);\n+\tcert = parse_certificate(keypath2, &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+\tfree(keypath2);\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\ndiff --git a/git-over-tls/main.c b/git-over-tls/main.c\nnew file mode 100644\nindex 0000000..9bc76b2\n--- /dev/null\n+++ b/git-over-tls/main.c\n@@ -0,0 +1,684 @@\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 \"ssh.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 <poll.h>\n+#include <gnutls/gnutls.h>\n+#include <signal.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+volatile sig_atomic_t in_handler = 0;\n+volatile sig_atomic_t sigusr1_flag = 0;\n+struct user *debug_for = NULL;\n+int verbose = 0;\n+static int socket_fd;\n+\n+static void sigusr1_handler(int x)\n+{\n+\tx = 0;\n+\tif (in_handler)\n+\t\tsigusr1_flag = 1;\n+\telse\n+\t\tuser_debug(debug_for, stderr);\n+}\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 *uservid;\t\t/* Unique server ID */\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+\tchar *transport_proto;\t/* Transport protocol. */\n+\tchar *address_space;\t/* Address space to use. May be NULL */\n+};\n+\n+void append_uniq_address(char *buffer, struct parsed_addr *_addr,\n+\tint default_flag)\n+{\n+\tchar *ptr;\n+\tptr = strchr(_addr->host, '~');\n+\tif (!ptr)\n+\t\tptr = _addr->host;\n+\telse\n+\t\tptr++;\n+\tif (strchr(ptr, ':'))\n+\t\tstrcat(buffer, \"[\");\n+\tstrcat(buffer, ptr);\n+\tif (strchr(_addr->host, ':'))\n+\t\tstrcat(buffer, \"]\");\n+\tif(!default_flag) {\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+\tconst char *proto_sep;\n+\tsize_t addrlen;\n+\tint nondefault_port = 0;\n+\tsize_t vhost_len;\n+\n+\t_addr.transport_proto = xstrdup(\"tcp\");\n+\t_addr.address_space = NULL;\n+\n+\taddrlen = strlen(addr);\n+\n+\tproto_end = strchr(addr, ':');\n+\tif (!proto_end)\n+\t\tdie(\"URL '%s': No ':' to end protocol.\", orig_addr);\n+\n+\t_addr.protocol = xstrndup(addr, proto_end - addr);\n+\tif (strncmp(proto_end, \"://\", 3))\n+\t\tdie(\"URL '%s': No '://' to end protocol.\", orig_addr);\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 = xstrndup(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 = xstrndup(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\tdie(\"URL '%s': Expected port or path after hostname\",\n+\t\t\t\t\torig_addr);\n+\t\t\tuhp_start = uhp_delim + 1;\n+\t\t} else\n+\t\t\tdie(\"URL '%s': Hostname has '[' without matching ']'\",\n+\t\t\t\torig_addr);\n+\t} else {\n+\t\tuhp_delim = strpbrk(uhp_start, \"[:/\");\n+\t\tif (*uhp_delim == '[')\n+\t\t\tdie(\"URL '%s': Unexpected '['\", orig_addr);\n+\t\t_addr.host = xstrndup(uhp_start, uhp_delim - uhp_start);\n+\t\tuhp_start = uhp_delim;\n+\t}\n+\n+\t_addr.uservid = NULL;\n+\tproto_sep = strchr(_addr.host, '@');\n+\tif (proto_sep && proto_sep != _addr.host)\n+\t{\n+\t\tchar *old_host;\n+\t\told_host = _addr.host;\n+\t\t_addr.uservid = xstrndup(_addr.host, proto_sep - _addr.host);\n+\t\t_addr.host = xstrdup(proto_sep + 1);\n+\t\tfree(old_host);\n+\t}\n+\n+\n+\tproto_sep = strchr(_addr.host, '~');\n+\tif (proto_sep)\n+\t{\n+\t\tfree(_addr.transport_proto);\n+\t\tfree(_addr.address_space);\n+\n+\t\tconst char *ptr2;\n+\t\tchar *old_host;\n+\t\told_host = _addr.host;\n+\t\tptr2 = strchr(old_host, '/');\n+\t\tif (ptr2 >= proto_sep)\n+\t\t\tptr2 = NULL;\n+\n+\t\tif (ptr2) {\n+\t\t\t_addr.transport_proto = xstrndup(_addr.host,\n+\t\t\t\tptr2 - old_host);\n+\t\t\t_addr.address_space = xstrndup(ptr2 + 1,\n+\t\t\t\tproto_sep - ptr2 - 1);\n+\t\t} else if (proto_sep == old_host + 4 &&\n+\t\t\t!strncmp(old_host, \"unix\", 4)) {\n+\t\t\t_addr.transport_proto = xstrdup(\"<N/A>\");\n+\t\t\t_addr.address_space = xstrdup(\"unix\");\n+\t\t} else {\n+\t\t\tchar suffix = '\\0';\n+\t\t\tif(proto_sep > old_host)\n+\t\t\t\tsuffix = proto_sep[-1];\n+\t\t\tif(suffix == '4') {\n+\t\t\t\t_addr.transport_proto = xstrndup(\n+\t\t\t\t\told_host, proto_sep - old_host - 1);\n+\t\t\t\t_addr.address_space = xstrdup(\"ipv4\");\n+\t\t\t} else if(suffix == '6') {\n+\t\t\t\t_addr.transport_proto = xstrndup(\n+\t\t\t\t\told_host, proto_sep - old_host - 1);\n+\t\t\t\t_addr.address_space = xstrdup(\"ipv6\");\n+\t\t\t} else if(suffix == '_') {\n+\t\t\t\t_addr.transport_proto = xstrndup(\n+\t\t\t\t\told_host, proto_sep - old_host - 1);\n+\t\t\t\t_addr.address_space = NULL;\n+\t\t\t} else {\n+\t\t\t\t_addr.transport_proto = xstrndup(\n+\t\t\t\t\told_host, proto_sep - old_host);\n+\t\t\t\t_addr.address_space = NULL;\n+\t\t\t}\n+\t\t}\n+\t\t_addr.host = xstrdup(proto_sep + 1);\n+\t\tfree(old_host);\n+\t}\n+\n+\tpath_start = strchr(uhp_start, '/');\n+\tif (!path_start)\n+\t\tdie(\"URL '%s': No '/' to start path\", orig_addr);\n+\n+\t_addr.path = xstrndup(path_start, addrlen - (path_start - addr));\n+\n+\tif (*uhp_start == ':')\n+\t\t_addr.port = xstrndup(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\tdie(\"URL '%s': Empty hostname not allowed\", orig_addr);\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 (!strcmp(_addr.protocol, \"git\") && _addr.uservid) {\n+\t\tdie(\"git:// does not support unique server identitifier\");\n+\t}\n+\n+\tif (_addr.port) {\n+\t\tnondefault_port = 1;\n+\t} else if (!strcmp(_addr.protocol, \"gits\")) {\n+\t\t_addr.port = xstrndup(\"git\", 3);\n+\t} else if (!strcmp(_addr.protocol, \"git\")) {\n+\t\t_addr.port = xstrndup(\"git\", 3);\n+\t} else if (!strcmp(_addr.protocol, \"tls\")) {\n+\t\t_addr.port = xstrndup(\"gits\", 4);\n+\t}\n+\n+\t/* 8 is for host=[]:\\0 */\n+\tvhost_len = 9 + strlen(_addr.host) + strlen(_addr.port);\n+\t_addr.vhost_header = xmalloc(vhost_len);\n+\n+\tstrcpy(_addr.vhost_header, \"host=\");\n+\tappend_uniq_address(_addr.vhost_header, &_addr, !nondefault_port);\n+\n+\tif (verbose) {\n+\t\tfprintf(stderr, \"Protocol:           %s\\n\", _addr.protocol);\n+\t\tfprintf(stderr, \"User:               %s\\n\", _addr.user ?\n+\t\t\t_addr.user :  \"<not set>\");\n+\t\tfprintf(stderr, \"Host:               %s\\n\", _addr.host);\n+\t\tfprintf(stderr, \"Unique server ID:   %s\\n\", _addr.uservid ?\n+\t\t\t_addr.uservid : \"<any>\");\n+\t\tfprintf(stderr, \"Port:               %s\\n\", _addr.port);\n+\t\tfprintf(stderr, \"Path:               %s\\n\", _addr.path);\n+\t\tfprintf(stderr, \"Vhost header:       %s\\n\",\n+\t\t\t_addr.vhost_header);\n+\t\tfprintf(stderr, \"Transport protocol: %s\\n\",\n+\t\t\t_addr.transport_proto);\n+\t\tfprintf(stderr, \"Address space:      %s\\n\",\n+\t\t\t_addr.address_space ? _addr.address_space :\n+\t\t\t\"<unspecified>\");\n+\t\tfprintf(stderr, \"\\n\");\n+\t}\n+\n+\treturn _addr;\n+}\n+\n+#define MODE_ALLOW_EOF 0\n+#define MODE_HANDSHAKE 1\n+#define MAXFDS 128\n+\n+static void disconnect_outgoing(struct user *user)\n+{\n+\tif(user_get_red_out(user))\n+\t\tuser_set_red_io(user, -1, -1, -1);\n+\telse\n+\t\tuser_set_red_io(user, -1, 1, -1);\n+\tuser_send_red_in_eof(user);\n+}\n+\n+static void traffic_loop(struct user *user, int mode)\n+{\n+\tfd_set rfds;\n+\tfd_set wfds;\n+\tstruct pollfd polled[MAXFDS];\n+\tint fdcount = 0;\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+\tfor(r = 0; r < bound || r <= socket_fd; r++) {\n+\t\tint added = 0;\n+\t\tpolled[fdcount].fd = r;\n+\t\tpolled[fdcount].events = 0;\n+\t\tpolled[fdcount].revents = 0;\n+\t\tif(FD_ISSET(r, &rfds)) {\n+\t\t\tpolled[fdcount].events |= (POLLIN | POLLHUP);\n+\t\t\tadded = 1;\n+\t\t}\n+\t\tif(FD_ISSET(r, &wfds)) {\n+\t\t\tpolled[fdcount].events |= (POLLOUT | POLLHUP);\n+\t\t\tadded = 1;\n+\t\t}\n+\t\tif(r == socket_fd) {\n+\t\t\tpolled[fdcount].events |= POLLHUP;\n+\t\t\tadded = 1;\n+\t\t}\n+\t\tif(added)\n+\t\t\tfdcount++;\n+\t}\n+\n+\tr = poll(polled, fdcount, -1);\n+\tif (r < 0 && errno != EINTR) {\n+\t\tdie_errno(\"poll() failed\");\n+\t} else if (r <= 0) {\n+\t\tFD_ZERO(&rfds);\n+\t\tFD_ZERO(&wfds);\n+\t} else {\n+\t\tFD_ZERO(&rfds);\n+\t\tFD_ZERO(&wfds);\n+\t\tfor(r = 0; r < fdcount; r++) {\n+\t\t\tif(polled[r].revents & POLLHUP) {\n+\t\t\t\t/* Write stream hangup. Disconnect. */\n+\t\t\t\tif(polled[r].fd == 0)\n+\t\t\t\t\tFD_SET(0, &rfds);\n+\t\t\t\tif(polled[r].fd == 1)\n+\t\t\t\t\tFD_SET(1, &wfds);\n+\t\t\t\tif(polled[r].fd > 1)\n+\t\t\t\t\tdisconnect_outgoing(user);\n+\t\t\t}\n+\t\t\tif(polled[r].revents & POLLIN)\n+\t\t\t\tFD_SET(polled[r].fd, &rfds);\n+\t\t\tif(polled[r].revents & POLLOUT)\n+\t\t\t\tFD_SET(polled[r].fd, &wfds);\n+\t\t}\n+\t}\n+\tin_handler = 1;\n+\tuser_service(user, &rfds, &wfds);\n+\tin_handler = 0;\n+\tif (sigusr1_flag) {\n+\t\tsigusr1_flag = 0;\n+\t\tuser_debug(debug_for, stderr);\n+\t}\n+\tfailcode = user_get_failure(user);\n+\tif (failcode)\n+\t\tgoto failed;\n+\tif (user_red_out_eofd(user) && !cbuffer_used(\n+\t\tuser_get_red_out_force(user))) {\n+\t\tfailcode = 1;\n+\t\tgoto failed;\n+\t}\n+\n+\treturn;\n+failed:\n+\tif (failcode > 0 && mode == MODE_ALLOW_EOF)\n+\t\treturn;\n+\telse if (failcode > 0) {\n+\t\terror(\"Expected more data, got connection closed\");\n+\t\tfprintf(stderr, \"Debug dump:\\n\");\n+\t\tuser_debug(user, stderr);\n+\t\tdie(\"Expected more data, got connection closed\");\n+\t} else if (failcode < 0) {\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+#endif\n+\tint kx[3];\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, \"ssh-\")) {\n+\t\t\tdo_ssh_preauth(username + 4);\n+\t\t\tkeypair_ok = 1;\n+\t\t\tgoto skip_ksel;\n+\t\t}\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+skip_ksel:\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+\tgoto skip_force;\n+no_srp:\n+\tkx[0] = GNUTLS_KX_DHE_DSS;\n+\tkx[1] = GNUTLS_KX_DHE_RSA;\n+\tkx[2] = 0;\n+\ts = gnutls_kx_set_priority(session, kx);\n+\tif (s < 0)\n+\t\tdie(\"Can't force Diffie-Hellman: %s\", gnutls_strerror(s));\n+skip_force:\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+\tif(hostname)\n+\t\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 suppress_ok, int no_repo)\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+\tint do_tls = 0;\n+\n+#ifdef SIGPIPE\n+\tsignal(SIGPIPE, SIG_IGN);\n+#endif\n+\n+\tpreconfigure_tls(addr->user);\n+\n+\tfd = connect_host(addr->host, addr->port,\n+\t\taddr->transport_proto, addr->address_space);\n+\n+\t/* Create dispatcher with no time limit. */\n+\tdebug_for = dispatcher = user_create(fd, 65535);\n+\tif (!dispatcher)\n+\t\tdie(\"Can't create connection context\");\n+\tsocket_fd = fd;\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\tif (verbose)\n+\t\t\tfprintf(stderr, \"Configuring TLS...\\n\");\n+\t\tconfigure_tls(dispatcher, addr->uservid);\n+\t\tdo_tls = 1;\n+\t} else {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Requesting TLS...\\n\");\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 (s >= 0 && !strcmp(tmpbuf, \"notsupp\\n\"))\n+\t\t\t\tdie(\"Server does not support gits://\");\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\tif (verbose)\n+\t\t\tfprintf(stderr, \"Server ready for TLS. Configuring TLS...\\n\");\n+\t\tconfigure_tls(dispatcher, addr->uservid);\n+\t\tdo_tls = 1;\n+\t}\n+\n+\tif (do_tls) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Waiting for TLS link to establish...\\n\");\n+\n+\t\twhile (!user_get_tls(dispatcher))\n+\t\t\ttraffic_loop(dispatcher, MODE_HANDSHAKE);\n+\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Secure link established.\\n\");\n+\t}\n+\n+\tif (addr->user && !prefixcmp(addr->user, \"ssh-\")) {\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Sending SSH auth request...\\n\");\n+\t\tsend_ssh_authentication(dispatcher, addr->user + 4);\n+\t\twhile (cbuffer_used(inbuf))\n+\t\t\ttraffic_loop(dispatcher, MODE_HANDSHAKE);\n+\t\tif (verbose)\n+\t\t\tfprintf(stderr, \"Sent SSH auth request.\\n\");\n+\t}\n+\n+\tif (!no_repo)\n+\t\treqsize = strlen(arg) + strlen(addr->path) +  3 +\n+\t\t\tstrlen(addr->vhost_header);\n+\telse\n+\t\treqsize = strlen(arg);\n+\n+\tif (reqsize > MAX_REQUEST)\n+\t\tdie(\"Request too big to send\");\n+\n+\tmemcpy(reqbuf + 4, arg, strlen(arg));\n+\tif (!no_repo) {\n+\t\treqbuf[strlen(arg) + 4] = ' ';\n+\t\tmemcpy(reqbuf + strlen(arg) + 5, addr->path, strlen(addr->path) + 1);\n+\t\tmemcpy(reqbuf + strlen(arg) + 6 + strlen(addr->path),\n+\t\t\taddr->vhost_header, strlen(addr->vhost_header) + 1);\n+\t}\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+\tif (verbose)\n+\t\tfprintf(stderr, \"Sending request...\\n\");\n+\tcbuffer_write(inbuf, (unsigned char*)reqbuf, reqsize + 4);\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Request sent, waiting for reply...\\n\");\n+\twhile (!cbuffer_used(outbuf) && !suppress_ok)\n+\t\ttraffic_loop(dispatcher, MODE_HANDSHAKE);\n+\tif (verbose)\n+\t\tfprintf(stderr, \"Server replied.\\n\");\n+\t/* Ok, remote end has replied. */\n+\tif(!suppress_ok)\n+\t\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 (getenv(\"GITS_VERBOSE\"))\n+\t\tverbose = 1;\n+\n+\tif (argc < 3) {\n+\t\tdie(\"Need two arguments\");\n+\t}\n+\n+\tsignal(SIGUSR1, sigusr1_handler);\n+\n+\tpaddr = parse_address(argv[2]);\n+\n+\tif (!prefixcmp(argv[1], \"--service=\")) {\n+\t\tdo_request(argv[1] + 10, &paddr, 1, 0);\n+\t\treturn 0;\n+\t}\n+\tif (!prefixcmp(argv[1], \"--nourl-service=\")) {\n+\t\tdo_request(argv[1] + 16, &paddr, 1, 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, 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/misc.c b/git-over-tls/misc.c\nnew file mode 100644\nindex 0000000..3d3e0b3\n--- /dev/null\n+++ b/git-over-tls/misc.c\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+#include \"misc.h\"\n+#include <unistd.h>\n+#include <errno.h>\n+\n+void force_close(int fd)\n+{\n+\twhile (close(fd) < 0 && errno != EBADF);\n+}\ndiff --git a/git-over-tls/misc.h b/git-over-tls/misc.h\nnew file mode 100644\nindex 0000000..b482f2c\n--- /dev/null\n+++ b/git-over-tls/misc.h\n@@ -0,0 +1,27 @@\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 _misc__h__included__\n+#define _misc__h__included__\n+\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+#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..b8eaacb\n--- /dev/null\n+++ b/git-over-tls/mkcert.c\n@@ -0,0 +1,474 @@\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+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 %lu \"\n+\t\t\t\"bytes).\\n\", (unsigned long)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 (!ensure_directory(\"$HOME\") < 0)\n+\t\tdie(\"Hey, you don't have a home directory!\");\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, \"$XDG_CONFIG_HOME/gits/keys/%s\", 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_create_dirs(fbuffer, O_WRONLY | O_CREAT | O_EXCL, 0600);\n+\tif (fd < 0) {\n+\t\terror(\"Can't open '%s': %s\", fbuffer, strerror(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..13b9f5e\n--- /dev/null\n+++ b/git-over-tls/srp_askpass.c\n@@ -0,0 +1,110 @@\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 *prompt,\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 widx = 0;\n+\tFILE *out;\n+\n+\tif (strchr(prompt, '\\\"'))\n+\t\tdie(\"Can't prompt for names containing '\\\"'\");\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(prompt) + 2 >= CMDBUFSIZE)\n+\t\t\t\tdie(\"Command line too long\");\n+\t\t\tcmdbuffer[widx++] = '\\\"';\n+\t\t\tstrcpy(cmdbuffer + widx, prompt);\n+\t\t\twidx += strlen(prompt);\n+\t\t\tcmdbuffer[widx++] = '\\\"';\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\tif (ferror(out))\n+\t\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_password(const char *prompt)\n+{\n+\tif (getenv(\"GITS_ASKPASS\"))\n+\t\treturn get_password_via_external(prompt,\n+\t\t\tgetenv(\"GITS_ASKPASS\"));\n+\n+\treturn prompt_string(prompt, 1);\n+}\n+\n+char *get_srp_password(const char *username)\n+{\n+\tstatic char buffer[PROMPTBUF + 1];\n+\tint len;\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+\treturn get_password(buffer);\n+}\n+\n+char *get_ssh_password(const char *keyname)\n+{\n+\tstatic char buffer[PROMPTBUF + 1];\n+\tint len;\n+\n+\tlen = snprintf(buffer, PROMPTBUF + 1, \"Enter passphrase to unlock %s\",\n+\t\tkeyname);\n+\tif (len < 0 || len > PROMPTBUF)\n+\t\tdie(\"Key name is insanely long\");\n+\n+\treturn get_password(buffer);\n+}\ndiff --git a/git-over-tls/srp_askpass.h b/git-over-tls/srp_askpass.h\nnew file mode 100644\nindex 0000000..cb79d34\n--- /dev/null\n+++ b/git-over-tls/srp_askpass.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 _srp_askpass__h__included__\n+#define _srp_askpass__h__included__\n+\n+/* Get password. Return is malloced */\n+char *get_password(const char *prompt);\n+char *get_srp_password(const char *username);\n+char *get_ssh_password(const char *keyname);\n+\n+#endif\n-- \n1.7.1.rc2.10.g714149\n"},{"id":"140686","messageId":"1272733791-11341-4-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"23654","inReplyTo":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[PATCH 3/3] git over TLS (gits://) support (part 3)","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2010-05-01T17:09:51Z","receivedAt":"2010-05-01T17:09:51Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n git-over-tls/pem.c          |  362 ++++++++++++++++++++++++\n git-over-tls/pem.h          |   16 +\n git-over-tls/pem_decrypt.c  |  203 ++++++++++++++\n git-over-tls/pem_decrypt.h  |    9 +\n git-over-tls/ssh.c          |  635 +++++++++++++++++++++++++++++++++++++++++++\n git-over-tls/ssh.h          |   18 ++\n git-over-tls/ssh_localkey.c |  435 +++++++++++++++++++++++++++++\n git-over-tls/ssh_localkey.h |   18 ++\n 8 files changed, 1696 insertions(+), 0 deletions(-)\n create mode 100644 git-over-tls/pem.c\n create mode 100644 git-over-tls/pem.h\n create mode 100644 git-over-tls/pem_decrypt.c\n create mode 100644 git-over-tls/pem_decrypt.h\n create mode 100644 git-over-tls/ssh.c\n create mode 100644 git-over-tls/ssh.h\n create mode 100644 git-over-tls/ssh_localkey.c\n create mode 100644 git-over-tls/ssh_localkey.h\n\ndiff --git a/git-over-tls/pem.c b/git-over-tls/pem.c\nnew file mode 100644\nindex 0000000..3e4f366\n--- /dev/null\n+++ b/git-over-tls/pem.c\n@@ -0,0 +1,362 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 \"pem.h\"\n+#include \"pem_decrypt.h\"\n+#include \"base64.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+void dump_blob(const char *name, const unsigned char *blob, size_t bloblen);\n+\n+static const unsigned char *strstr_n(const unsigned char *haystack,\n+\tconst unsigned char *needle, size_t haystack_size)\n+{\n+\tsize_t maxpos, i;\n+\tsize_t needle_size;\n+\tneedle_size = strlen((const char*)needle);\n+\tif (haystack_size < needle_size)\n+\t\treturn NULL;\n+\tmaxpos = haystack_size - needle_size;\n+\tfor (i = 0; i < maxpos; i++)\n+\t\tif (!strncmp((const char*)(haystack + i),\n+\t\t\t(const char*)needle, needle_size))\n+\t\t\treturn haystack + i;\n+\treturn NULL;\n+}\n+\n+static const unsigned char *strchrs_n(const unsigned char *str,\n+\tconst char *chars, size_t str_len)\n+{\n+\twhile (str_len > 0) {\n+\t\tif (strchr(chars, (char)str[0]))\n+\t\t\treturn str;\n+\t\tstr++;\n+\t\tstr_len--;\n+\t}\n+\treturn NULL;\n+}\n+\n+const unsigned char *find_base64_start(const unsigned char *start,\n+\tconst unsigned char *end, char **dek_info)\n+{\n+\tconst unsigned char *startaddr;\n+\tconst unsigned char *dekline;\n+\tsize_t remaining = end - start;\n+\tstartaddr = base64_blob_start(start, &remaining);\n+\tif (!startaddr) {\n+\t\terror(\"Malformed PEM: No BASE64 section found\");\n+\t\treturn NULL;\n+\t}\n+\tdekline = strstr_n(start, (const unsigned char*)\"DEK-Info: \",\n+\t\tstartaddr - start);\n+\tif (!dekline)\n+\t\t*dek_info = NULL;\n+\telse {\n+\t\tconst unsigned char *term;\n+\t\tconst unsigned char *dekstart = dekline + 10;\n+\t\twhile (dekstart < startaddr && (*dekstart == ' ' ||\n+\t\t\t*dekstart == '\\t'))\n+\t\t\tdekstart++;\n+\n+\t\tterm = strchrs_n(dekstart, \"\\r\\n\", startaddr - dekstart);\n+\t\tif(!term) {\n+\t\t\terror(\"Malformed PEM: No BASE64 section found\");\n+\t\t\treturn NULL;\n+\t\t}\n+\t\t*dek_info = xstrndup((const char*)dekstart, term - dekstart);\n+\t}\n+\treturn startaddr;\n+}\n+\n+size_t decode_asn1_length_tag(const unsigned char **file, size_t *left)\n+{\n+\tif (*left < 1)\n+\t\tgoto eof;\n+\n+\tif (**file < 0x80) {\n+\t\t(*left)--;\n+\t\t(*file)++;\n+\t\treturn (*file)[-1];\n+\t} else if (**file == 0x80) {\n+\t\terror(\"Malformed PEM: Bad length tag\");\n+\t\t*file = NULL;\n+\t\treturn 0;\n+\t} else if (**file == 0x81) {\n+\t\tif (*left < 2)\n+\t\t\tgoto eof;\n+\t\t*left -= 2;\n+\t\t(*file) += 2;\n+\t\treturn (*file)[-1];\n+\t} else if (**file == 0x82) {\n+\t\tif (*left < 3)\n+\t\t\tgoto eof;\n+\t\t*left -= 3;\n+\t\t(*file) += 3;\n+\t\treturn (((size_t)(*file)[-2]) << 8) |\n+\t\t\t((size_t)(*file)[-1]);\n+\t} else if (**file == 0x83) {\n+\t\tif (*left < 4)\n+\t\t\tgoto eof;\n+\t\t*left -= 4;\n+\t\t(*file) += 4;\n+\t\treturn (((size_t)(*file)[-3]) << 16) |\n+\t\t\t(((size_t)(*file)[-2]) << 8) |\n+\t\t\t((size_t)(*file)[-1]);\n+\t} else {\n+\t\terror(\"Unreadable PEM: Size too large\");\n+\t\t*file = NULL;\n+\t\treturn 0;\n+\t}\n+eof:\n+\terror(\"Malformed PEM: Expected length tag, got EOF\");\n+\t*file = NULL;\n+\treturn 0;\n+}\n+\n+size_t asn1_integers_size(const unsigned char *seq, size_t seq_len,\n+\tint low, int high, size_t *k2size)\n+{\n+\tint component = 0;\n+\tsize_t size = 0;\n+\tfor (component = 0; component < high; component++) {\n+\t\tsize_t clen = 0;\n+\t\tif (seq_len < 1) {\n+\t\t\terror(\"Malformed PEM: Expected tag, got EOF\");\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (*seq != 0x02) {\n+\t\t\terror(\"Malformed PEM: Expected INTEGER\");\n+\t\t\treturn 0;\n+\t\t}\n+\t\tseq++;\n+\t\tseq_len--;\n+\t\tclen = decode_asn1_length_tag(&seq, &seq_len);\n+\t\tif (!seq)\n+\t\t\treturn 0;\n+\t\tif (seq_len < clen) {\n+\t\t\terror(\"Malformed PEM: Truncated integer\");\n+\t\t\treturn 0;\n+\t\t}\n+\t\tseq += clen;\n+\t\tseq_len -= clen;\n+\t\tif (component == 2 && k2size)\n+\t\t\t*k2size = (4 + clen);\n+\t\tif (component >= low) {\n+\t\t\tsize += (4 + clen);\n+\t\t}\n+\t}\n+\treturn size;\n+}\n+\n+unsigned char *handle_asn1_sequence(const unsigned char *seq, size_t seq_len,\n+\tsize_t *keysize, int keytype)\n+{\n+\tint low;\n+\tint high;\n+\tsize_t kslow;\n+\tsize_t toffset = 0;\n+\tsize_t foffset = 0;\n+\tint component = 0;\n+\tsize_t offset = 0;\n+\tsize_t k2size = 0;\n+\tunsigned char *ret = NULL;\n+\n+\tif (keytype == 1) {\n+\t\tlow = 1;\n+\t\thigh = 4;\n+\t\tkslow = 11;\n+\t} else if (keytype == 2) {\n+\t\tlow = 1;\n+\t\thigh = 6;\n+\t\tkslow = 11;\n+\t} else {\n+\t\terror(\"Unknown PEM key type\");\n+\t\treturn NULL;\n+\t}\n+\t*keysize = kslow + asn1_integers_size(seq, seq_len, low, high,\n+\t\t&k2size);\n+\tif (*keysize <= kslow)\n+\t\treturn NULL;\n+\tret = xmalloc(*keysize);\n+\n+\tif (keytype == 1) {\n+\t\tret[0] = 0;\n+\t\tret[1] = 0;\n+\t\tret[2] = 0;\n+\t\tret[3] = 7;\n+\t\tmemcpy(ret + 4, \"ssh-rsa\", 7);\n+\t\toffset = 11;\n+\t} else if (keytype == 2) {\n+\t\tret[0] = 0;\n+\t\tret[1] = 0;\n+\t\tret[2] = 0;\n+\t\tret[3] = 7;\n+\t\tmemcpy(ret + 4, \"ssh-dss\", 7);\n+\t\toffset = 11;\n+\t}\n+\n+\t/*\n+\t * Hack warning: The e and n are in \"wrong\" order in PEM\n+\t * RSA keys, so swap those.\n+\t */\n+\tfor (component = 0; component < high; component++) {\n+\t\tsize_t clen = 0;\n+\t\tseq++;\n+\t\tseq_len--;\n+\t\tclen = decode_asn1_length_tag(&seq, &seq_len);\n+\t\tif(component >= low) {\n+\t\t\tif (keytype == 1 && component == 1) {\n+\t\t\t\ttoffset = offset;\n+\t\t\t\tfoffset = offset + 4 + clen;\n+\t\t\t\toffset += k2size;\n+\t\t\t}\n+\t\t\tret[offset + 0] = (unsigned char)(clen >> 24);\n+\t\t\tret[offset + 1] = (unsigned char)(clen >> 16);\n+\t\t\tret[offset + 2] = (unsigned char)(clen >> 8);\n+\t\t\tret[offset + 3] = (unsigned char)(clen);\n+\t\t\tmemcpy(ret + offset + 4, seq, clen);\n+\t\t\toffset += (4 + clen);\n+\t\t\tif (keytype == 1 && component == 1)\n+\t\t\t\toffset = toffset;\n+\t\t\tif (keytype == 1 && component == 2)\n+\t\t\t\toffset = foffset + k2size;\n+\t\t}\n+\t\tseq += clen;\n+\t\tseq_len -= clen;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+\n+#define RSA_START_MARKER \"-----BEGIN RSA PRIVATE KEY-----\"\n+#define DSA_START_MARKER \"-----BEGIN DSA PRIVATE KEY-----\"\n+#define START_MARKER_LEN 31\n+#define RSA_END_MARKER \"-----END RSA PRIVATE KEY-----\"\n+#define DSA_END_MARKER \"-----END DSA PRIVATE KEY-----\"\n+\n+unsigned char *pem_decode(const unsigned char *file, size_t filesize,\n+\tconst char *keyname, size_t *keysize)\n+{\n+\tint type = 0;\n+\tconst unsigned char *start;\n+\tconst unsigned char *end;\n+\tunsigned char *binary;\n+\tunsigned char *ret;\n+\tconst unsigned char *binary_c;\n+\tsize_t binary_c_size;\n+\tsize_t binary_size;\n+\tsize_t length;\n+\tchar *dek_info;\n+\n+\twhile(filesize > 0 && (*file == '\\n' || *file == '\\r')) {\n+\t\tfilesize--;\n+\t\tfile++;\n+\t}\n+\n+\tif (filesize < 64)\n+\t\treturn NULL;\n+\n+\tif (!strncmp((const char*)file, RSA_START_MARKER \"\\r\",\n+\t\tSTART_MARKER_LEN + 1)) {\n+\t\tstart = file + START_MARKER_LEN + 1;\n+\t\ttype = 1;\n+\t} else if (!strncmp((const char*)file,\n+\t\tDSA_START_MARKER \"\\r\", START_MARKER_LEN + 1)) {\n+\t\tstart = file + START_MARKER_LEN + 1;\n+\t\ttype = 2;\n+\t} else if (!strncmp((const char*)file,\n+\t\tRSA_START_MARKER \"\\n\", START_MARKER_LEN + 1)) {\n+\t\tstart = file + START_MARKER_LEN + 1;\n+\t\ttype = 1;\n+\t} else if (!strncmp((const char*)file,\n+\t\tDSA_START_MARKER \"\\n\", START_MARKER_LEN + 1)) {\n+\t\tstart = file + START_MARKER_LEN + 1;\n+\t\ttype = 2;\n+\t} else {\n+\t\treturn NULL;\n+\t}\n+\n+\tif(type == 1) {\n+\t\tend = strstr_n(file, (const unsigned char*)\n+\t\t\t\"\\r\" RSA_END_MARKER, filesize);\n+\t\tif (end)\n+\t\t\tgoto ok;\n+\t\tend = strstr_n(file, (const unsigned char*)\n+\t\t\t\"\\n\" RSA_END_MARKER, filesize);\n+\t\tif (end)\n+\t\t\tgoto ok;\n+\t\terror(\"Malformed PEM: No end marker (type 1)\");\n+\t\treturn NULL;\n+\t} else if(type == 2) {\n+\t\tend = strstr_n(file, (const unsigned char*)\n+\t\t\t\"\\r\" DSA_END_MARKER, filesize);\n+\t\tif (end)\n+\t\t\tgoto ok;\n+\t\tend = strstr_n(file, (const unsigned char*)\n+\t\t\t\"\\n\" DSA_END_MARKER, filesize);\n+\t\tif (end)\n+\t\t\tgoto ok;\n+\t\terror(\"Malformed PEM: No end marker (type 2)\");\n+\t\treturn NULL;\n+\t} else {\n+\t\terror(\"Unknown PEM key type\");\n+\t\treturn NULL;\n+\t}\n+ok:\n+\tstart = find_base64_start(start, end, &dek_info);\n+\tbinary = decode_base64_chunk(start, end - start, &binary_size);\n+\tif (!binary) {\n+\t\tfree(dek_info);\n+\t\treturn NULL;\n+\t}\n+\tif (dek_info) {\n+\t\tunsigned char *decrypted;\n+\t\tsize_t decrypted_len;\n+\t\tdecrypted = decrypt_key(binary, binary_size, dek_info,\n+\t\t\tkeyname, &decrypted_len);\n+\t\tfree(dek_info);\n+\t\tfree(binary);\n+\t\tif (!decrypted)\n+\t\t\treturn NULL;\n+\t\tbinary = decrypted;\n+\t\tbinary_size = decrypted_len;\n+\t}\n+\tif (binary_size < 1)\n+\t\tgoto invalid_seq;\n+\tif (binary[0] != 0x30) {\n+\t\terror(\"Malformed PEM: First element must be SEQUENCE\");\n+\t\tgoto error_decoded;\n+\t}\n+\tbinary_c = binary + 1;\n+\tbinary_c_size = binary_size -1;\n+\tlength = decode_asn1_length_tag(&binary_c, &binary_c_size);\n+\tif (!binary_c) {\n+\t\terror(\"Malformed PEM: Bad length for SEQUENCE\");\n+\t\tgoto error_decoded;\n+\t}\n+\tif (length < binary_c_size) {\n+\t\terror(\"Malformed PEM: Garbage after end\");\n+\t\tgoto error_decoded;\n+\t} else if (length > binary_c_size) {\n+\t\terror(\"Malformed PEM: SEQUENCE overflows file\");\n+\t\tgoto error_decoded;\n+\t}\n+\tret = handle_asn1_sequence(binary_c, binary_c_size, keysize, type);\n+\tfree(binary);\n+\treturn ret;\n+invalid_seq:\n+\terror(\"Malformed PEM: No valid sequence\");\n+error_decoded:\n+\tfree(binary);\n+\treturn NULL;\n+}\ndiff --git a/git-over-tls/pem.h b/git-over-tls/pem.h\nnew file mode 100644\nindex 0000000..df202e5\n--- /dev/null\n+++ b/git-over-tls/pem.h\n@@ -0,0 +1,16 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 _pem__h__included__\n+#define _pem__h__included__\n+\n+#include <stdlib.h>\n+\n+unsigned char *pem_decode(const unsigned char *file, size_t filesize,\n+\tconst char* keyname, size_t *keysize);\n+\n+#endif\ndiff --git a/git-over-tls/pem_decrypt.c b/git-over-tls/pem_decrypt.c\nnew file mode 100644\nindex 0000000..46effcf\n--- /dev/null\n+++ b/git-over-tls/pem_decrypt.c\n@@ -0,0 +1,203 @@\n+#include \"pem_decrypt.h\"\n+#include \"srp_askpass.h\"\n+#include <string.h>\n+#include <gcrypt.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+#define BUFFERSIZE 16384\n+#define IVSIZE 128\n+\n+unsigned char hexdecode(char c)\n+{\n+\tswitch(c) {\n+\tcase '0':\treturn 0;\n+\tcase '1':\treturn 1;\n+\tcase '2':\treturn 2;\n+\tcase '3':\treturn 3;\n+\tcase '4':\treturn 4;\n+\tcase '5':\treturn 5;\n+\tcase '6':\treturn 6;\n+\tcase '7':\treturn 7;\n+\tcase '8':\treturn 8;\n+\tcase '9':\treturn 9;\n+\tcase 'a':\treturn 10;\n+\tcase 'b':\treturn 11;\n+\tcase 'c':\treturn 12;\n+\tcase 'd':\treturn 13;\n+\tcase 'e':\treturn 14;\n+\tcase 'f':\treturn 15;\n+\tcase 'A':\treturn 10;\n+\tcase 'B':\treturn 11;\n+\tcase 'C':\treturn 12;\n+\tcase 'D':\treturn 13;\n+\tcase 'E':\treturn 14;\n+\tcase 'F':\treturn 15;\n+\tdefault:\tdie(\"Bad hexadecimal character\");\n+\t}\n+\treturn 0;\n+}\n+\n+unsigned char hexpairdecode(const char* hexpair)\n+{\n+\treturn hexdecode(hexpair[0]) * 16 + hexdecode(hexpair[1]);\n+}\n+\n+void decode_iv(unsigned char *iv, const char *dek_tail, size_t correct_len)\n+{\n+\tint i;\n+\tif (strchr(dek_tail, ' ') != dek_tail + 2 * correct_len &&\n+\t\tstrlen(dek_tail) != 2 * correct_len)\n+\t\tdie(\"IV field in key encryption wrong length\");\n+\n+\tfor (i = 0; i < correct_len; i++)\n+\t\tiv[i] = hexpairdecode(dek_tail + 2 * i);\n+}\n+\n+#define MD5LEN 16\n+#define SALTLEN 8\n+\n+void derive_key(unsigned char *key, size_t keylen, const unsigned char *iv,\n+\tconst char *passphrase)\n+{\n+\tsize_t passphrase_len;\n+\tint derived;\n+\tunsigned char* md5 = NULL;\n+\tunsigned char md5copy[MD5LEN];\n+\tgcry_md_hd_t handle;\n+\tint i;\n+\n+\tif (gcry_md_open(&handle, GCRY_MD_MD5, 0))\n+\t\tdie(\"Can't gcry_md_open(..., GCRY_MD_MD5, ...).\");\n+\n+\tpassphrase_len = strlen(passphrase);\n+\tfor (derived = 0; derived < keylen;) {\n+\t\tgcry_md_reset(handle);\n+\n+\t\tif (md5)\n+\t\t\tgcry_md_write(handle, md5copy, MD5LEN);\n+\t\tgcry_md_write(handle, passphrase, passphrase_len);\n+\t\tgcry_md_write(handle, iv, SALTLEN);\n+\n+\t\tmd5 = gcry_md_read(handle, 0);\n+\t\tmemcpy(md5copy, md5, MD5LEN);\n+\t\tfor (i = 0; i < 16 && derived < keylen; i++) {\n+\t\t\tkey[derived++] = md5[i];\n+\t\t}\n+\t}\n+\tgcry_md_close(handle);\n+}\n+\n+unsigned char *decrypt_key(const unsigned char *cipher, size_t cipher_size,\n+\tconst char *dek_info, const char *keyname, size_t *decrypted_len)\n+{\n+\tchar *passphrase;\n+\tstatic char *stored_passphrase = NULL;\n+\tstatic char lastkeyname[BUFFERSIZE];\n+\tunsigned char iv[IVSIZE];\n+\tunsigned char key[IVSIZE];\n+\tsize_t ivlen;\n+\tsize_t keylen;\n+\tint algo;\n+\tint mode;\n+\tsize_t i;\n+\tint cipher_inited = 0;\n+\tgcry_cipher_hd_t cipherh;\n+\tunsigned char *ret = NULL;\n+\n+\tif (strlen(keyname) + 1 > BUFFERSIZE)\n+\t\tdie(\"Keyname insanely long\");\n+\n+\tif (stored_passphrase && !strcmp(keyname, lastkeyname)) {\n+\t\tpassphrase = stored_passphrase;\n+\t\tgoto reuse_passphrase;\n+\t}\n+\n+\tpassphrase = get_ssh_password(keyname);\n+\tstored_passphrase = passphrase;\n+reuse_passphrase:\n+\tif (!prefixcmp(dek_info, \"DES-EDE3-CBC,\")) {\n+\t\t/* 3DES-CBC */\n+\t\tivlen = 8;\n+\t\tkeylen = 24;\n+\t\talgo = GCRY_CIPHER_3DES;\n+\t\tmode = GCRY_CIPHER_MODE_CBC;\n+\t} else if (!prefixcmp(dek_info, \"DES-CBC,\")) {\n+\t\t/* DES-CBC (INSECURE!!!) */\n+\t\tivlen = 8;\n+\t\tkeylen = 8;\n+\t\talgo = GCRY_CIPHER_DES;\n+\t\tmode = GCRY_CIPHER_MODE_CBC;\n+\t} else if (!prefixcmp(dek_info, \"AES-128-CBC,\")) {\n+\t\t/* AES-128-CBC */\n+\t\tivlen = 16;\n+\t\tkeylen = 16;\n+\t\talgo = GCRY_CIPHER_AES128;\n+\t\tmode = GCRY_CIPHER_MODE_CBC;\n+\t} else if (!prefixcmp(dek_info, \"AES-192-CBC,\")) {\n+\t\t/* AES-192-CBC */\n+\t\tivlen = 16;\n+\t\tkeylen = 24;\n+\t\talgo = GCRY_CIPHER_AES192;\n+\t\tmode = GCRY_CIPHER_MODE_CBC;\n+\t} else if (!prefixcmp(dek_info, \"AES-256-CBC,\")) {\n+\t\t/* AES-256-CBC */\n+\t\tivlen = 16;\n+\t\tkeylen = 32;\n+\t\talgo = GCRY_CIPHER_AES256;\n+\t\tmode = GCRY_CIPHER_MODE_CBC;\n+\t} else {\n+\t\terror(\"Key encryption type not supported (%s)\", dek_info);\n+\t\tgoto error_exit;\n+\t}\n+\tdecode_iv(iv, strchr(dek_info, ',') + 1, ivlen);\n+\tderive_key(key, keylen, iv, passphrase);\n+\n+\tif (gcry_cipher_open(&cipherh, algo, mode, 0)) {\n+\t\terror(\"gcry_cipher_open() failed (DEK=%s; unsupported algorithm?)\", dek_info);\n+\t\tgoto error_exit;\n+\t}\n+\tcipher_inited = 1;\n+\n+\tif (gcry_cipher_setkey(cipherh, key, keylen)) {\n+\t\terror(\"gcry_cipher_setkey() failed (DEK=%s)\", dek_info);\n+\t\tgoto error_exit;\n+\t}\n+\n+\tif (gcry_cipher_setiv(cipherh, iv, ivlen)) {\n+\t\terror(\"gcry_cipher_setiv() failed (DEK=%s)\", dek_info);\n+\t\tgoto error_exit;\n+\t}\n+\n+\tret = xmalloc(cipher_size);\n+\tif (gcry_cipher_decrypt(cipherh, ret, cipher_size, cipher,\n+\t\tcipher_size)) {\n+\t\terror(\"gcry_cipher_decrypt() failed (DEK=%s, wrong passphrase?)\", dek_info);\n+\t\tgoto error_exit;\n+\t}\n+\n+\t*decrypted_len = cipher_size - ret[cipher_size - 1];\n+\tfor(i = *decrypted_len; i < cipher_size; i++) {\n+\t\tif(ret[i] != ret[cipher_size - 1]) {\n+\t\t\terror(\"Incorrect passphrase or key corrupt\");\n+\t\t\tgoto error_exit;\n+\t\t}\n+\t}\n+\n+\tstrcpy(lastkeyname, keyname);\n+\tstored_passphrase = passphrase;\n+\tgcry_cipher_close(cipherh);\n+\treturn ret;\n+error_exit:\n+\tif (cipher_inited)\n+\t\tgcry_cipher_close(cipherh);\n+\tif (stored_passphrase)\n+\t\tfree(stored_passphrase);\n+\tif (ret)\n+\t\tfree(ret);\n+\tstored_passphrase = NULL;\n+\treturn NULL;\n+}\ndiff --git a/git-over-tls/pem_decrypt.h b/git-over-tls/pem_decrypt.h\nnew file mode 100644\nindex 0000000..ab132bf\n--- /dev/null\n+++ b/git-over-tls/pem_decrypt.h\n@@ -0,0 +1,9 @@\n+#ifndef _pem_decrypt__h__included__\n+#define _pem_decrypt__h__included__\n+\n+#include <stdlib.h>\n+\n+unsigned char *decrypt_key(const unsigned char *cipher, size_t cipher_size,\n+\tconst char *dek_info, const char *keyname, size_t *decrypted_len);\n+\n+#endif\ndiff --git a/git-over-tls/ssh.c b/git-over-tls/ssh.c\nnew file mode 100644\nindex 0000000..1d7426f\n--- /dev/null\n+++ b/git-over-tls/ssh.c\n@@ -0,0 +1,635 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 \"ssh.h\"\n+#include \"ssh_localkey.h\"\n+#include \"home.h\"\n+#include \"base64.h\"\n+#include <stdlib.h>\n+#include <string.h>\n+#include <errno.h>\n+#include <sys/un.h>\n+#include <sys/socket.h>\n+#include <sys/types.h>\n+#include <unistd.h>\n+#include <gcrypt.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+#include <stdlib.h>\n+\n+#define SSH2_AGENTC_SIGN_REQUEST 13\n+#define SSH_AGENT_FAILURE 5\n+#define SSH_AGENT_SUCCESS 6\n+#define SSH2_AGENT_SIGN_RESPONSE 14\n+\n+#define MAX_REPLY_SIZE 1048576\n+\n+/* Decode uint32. */\n+static unsigned long decode_uint32(const unsigned char *ptr)\n+{\n+\tunsigned long v = 0;\n+\tv |= ((unsigned long)ptr[0] << 24);\n+\tv |= ((unsigned long)ptr[1] << 16);\n+\tv |= ((unsigned long)ptr[2] << 8);\n+\tv |= ((unsigned long)ptr[3]);\n+\treturn v;\n+}\n+\n+/*\n+ * Extract actual signature from ssh-agent reply. The return value should be\n+ * freed. The reply has the component lengths present.\n+ */\n+static unsigned char *extract_actual_key(const unsigned char *keyreply,\n+\tsize_t keyreply_len, const char *expected_type, size_t *key_len)\n+{\n+\tunsigned long rlen;\n+\n+\tif (keyreply_len < 1) {\n+\t\terror(\"Malformed reply from SSH agent: No packet type present\");\n+\t\treturn NULL;\n+\t}\n+\tif (keyreply[0] == SSH_AGENT_FAILURE) {\n+\t\terror(\"SSH agent failed to sign the challenge\");\n+\t\treturn NULL;\n+\t}\n+\tif (keyreply[0] != SSH2_AGENT_SIGN_RESPONSE) {\n+\t\terror(\"SSH agent is confused: Returned packet type %u when type %u was expected\",\n+\t\t\tkeyreply[0], SSH2_AGENT_SIGN_RESPONSE);\n+\t\treturn NULL;\n+\t}\n+\tif (keyreply_len < 5) {\n+\t\terror(\"Malformed signature reply from SSH agent: Signature length incomplete\");\n+\t\treturn NULL;\n+\t}\n+\trlen = decode_uint32(keyreply + 1);\n+\n+\tif (keyreply_len < 5 + rlen) {\n+\t\terror(\"Malformed signature reply from SSH agent: Signature incomplete\");\n+\t\treturn NULL;\n+\t}\n+\t/* Strip packet header. */\n+\tkeyreply += 5;\n+\tkeyreply_len = rlen;\n+\n+\tif (keyreply_len < 4) {\n+\t\terror(\"Malformed signature reply from SSH agent: Keytype header incomplete\");\n+\t\treturn NULL;\n+\t}\n+\trlen = decode_uint32(keyreply);\n+\tif (keyreply_len < 4 + rlen) {\n+\t\terror(\"Malformed signature reply from SSH agent: Keytype incomplete\");\n+\t\treturn NULL;\n+\t}\n+\tif (rlen != strlen(expected_type) || memcmp(keyreply + 4,\n+\t\texpected_type, rlen)) {\n+\t\terror(\"SSH agent returned signature of wrong type: Expected '%s', got '%.*s'\",\n+\t\t\texpected_type, (int)rlen, keyreply + 4);\n+\t\treturn NULL;\n+\t}\n+\t/* Strip key type. */\n+\tkeyreply += 4 + rlen;\n+\tkeyreply_len -= 4 + rlen;\n+\n+\tif (keyreply_len < 4) {\n+\t\terror(\"Malformed signature reply from SSH agent: Signature blob length incomplete\");\n+\t\treturn NULL;\n+\t}\n+\trlen = decode_uint32(keyreply);\n+\tif (keyreply_len < 4 + rlen) {\n+\t\terror(\"Malformed signature reply from SSH agent: Signature blob incomplete\");\n+\t\treturn NULL;\n+\t}\n+\n+\t/* Extract the signature blob. */\n+\tkeyreply += 4;\n+\tkeyreply_len = rlen;\n+\n+\tif (!strcmp(expected_type, \"ssh-rsa\")) {\n+\t\tunsigned char *ret;\n+\n+\t\tret = xmalloc(keyreply_len + 5);\n+\t\tif (keyreply[0] > 128) {\n+\t\t\t/* Zero-pad to avoid negative value. */\n+\t\t\tencode_uint32(ret, keyreply_len + 1);\n+\t\t\tret[4] = 0;\n+\t\t\tmemcpy(ret + 5, keyreply, keyreply_len);\n+\t\t\t*key_len = 5 + keyreply_len;\n+\t\t} else {\n+\t\t\tencode_uint32(ret, keyreply_len);\n+\t\t\tmemcpy(ret + 4, keyreply, keyreply_len);\n+\t\t\t*key_len = 4 + keyreply_len;\n+\t\t}\n+\t\treturn ret;\n+\t} else if (!strcmp(expected_type, \"ssh-dss\")) {\n+\t\tunsigned char *ret;\n+\t\tunsigned offset = 0;\n+\n+\t\tret = xmalloc(keyreply_len + 10);\n+\t\tif (keyreply_len % 2) {\n+\t\t\terror(\"Malformed DSA signature, length is odd.\");\n+\t\t\tfree(ret);\n+\t\t\treturn NULL;\n+\t\t}\n+\n+\t\t/* Encode r. */\n+\t\tif (keyreply[0] > 128) {\n+\t\t\t/* Zero-pad to avoid negative value. */\n+\t\t\tencode_uint32(ret, keyreply_len / 2 + 1);\n+\t\t\tret[offset + 4] = 0;\n+\t\t\toffset += 5;\n+\t\t} else {\n+\t\t\tencode_uint32(ret, keyreply_len / 2);\n+\t\t\toffset += 4;\n+\t\t}\n+\t\tmemcpy(ret + offset, keyreply, keyreply_len / 2);\n+\t\toffset += keyreply_len / 2;\n+\n+\t\t/* Encode s. */\n+\t\tif (keyreply[keyreply_len / 2] > 128) {\n+\t\t\t/* Zero-pad to avoid negative value. */\n+\t\t\tencode_uint32(ret + offset, keyreply_len / 2 + 1);\n+\t\t\tret[offset + 4] = 0;\n+\t\t\toffset += 5;\n+\t\t} else {\n+\t\t\tencode_uint32(ret + offset, keyreply_len / 2);\n+\t\t\toffset += 4;\n+\t\t}\n+\t\tmemcpy(ret + offset, keyreply + keyreply_len / 2,\n+\t\t\tkeyreply_len / 2);\n+\t\toffset += keyreply_len / 2;\n+\n+\t\t*key_len = offset;\n+\t\treturn ret;\n+\t} else {\n+\t\terror(\"Unknown signature type '%s'\", expected_type);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+\n+/* Extract SecSH public key. */\n+static unsigned char *extract_secsh_pubkey(const unsigned char *file,\n+\tsize_t file_len, size_t *key_len)\n+{\n+\tconst unsigned char *file2;\n+\tsize_t file_len2;\n+\n+\t/* Skip to begin line. */\n+\tif (*file == '\\r' || *file == '\\n') {\n+\t\t/* Skip the empty lines. */\n+\t\tfile = next_line(file, &file_len);\n+\t\tif (!file) {\n+\t\t\terror(\"Malformed SecSH public key (no header)\");\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\tfile = next_line(file, &file_len);\n+\tif (!file) {\n+\t\terror(\"Malformed SecSH public key (no header)\");\n+\t\treturn NULL;\n+\t}\n+\n+\tfile = base64_blob_start(file, &file_len);\n+\tfile2 = file;\n+\tfile_len2 = file_len;\n+\n+\tif (file_len2 == 29 && !memcmp(file2,\n+\t\t\"---- END SSH2 PUBLIC KEY ----\", 29)) {\n+\t\terror(\"Malformed SecSH public key (no encoded blob)\");\n+\t\treturn NULL;\n+\t}\n+\tif (!memcmp(file2, \"---- END SSH2 PUBLIC KEY ----\\r\", 30)) {\n+\t\terror(\"Malformed SecSH public key (no encoded blob)\");\n+\t\treturn NULL;\n+\t}\n+\tif (!memcmp(file2, \"---- END SSH2 PUBLIC KEY ----\\n\", 30)) {\n+\t\terror(\"Malformed SecSH public key (no encoded blob)\");\n+\t\treturn NULL;\n+\t}\n+\twhile(1) {\n+\t\tfile2 = next_line(file2, &file_len2);\n+\t\tif (!file2) {\n+\t\t\terror(\"Malformed SecSH public key (no trailer)\");\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tif (file_len2 == 29 && !memcmp(file2,\n+\t\t\t\"---- END SSH2 PUBLIC KEY ----\", 29)) {\n+\t\t\tfile_len = file2 - file;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!memcmp(file2, \"---- END SSH2 PUBLIC KEY ----\\r\", 30)) {\n+\t\t\tfile_len = file2 - file;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!memcmp(file, \"---- END SSH2 PUBLIC KEY ----\\n\", 30)) {\n+\t\t\tfile_len = file2 - file;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn decode_base64_chunk(file, file_len, key_len);\n+}\n+\n+/* Is SecSH format key? */\n+static int is_secsh_pubkey(const unsigned char *file, size_t file_len)\n+{\n+\tif (file_len < 32)\n+\t\treturn 0;\n+\tif (!memcmp(file, \"---- BEGIN SSH2 PUBLIC KEY ----\\r\", 32))\n+\t\treturn 1;\n+\tif (!memcmp(file, \"---- BEGIN SSH2 PUBLIC KEY ----\\n\", 32))\n+\t\treturn 1;\n+\tfile = next_line(file, &file_len);\n+\tif (file_len < 32)\n+\t\treturn 0;\n+\tif (!memcmp(file, \"---- BEGIN SSH2 PUBLIC KEY ----\\r\", 32))\n+\t\treturn 1;\n+\tif (!memcmp(file, \"---- BEGIN SSH2 PUBLIC KEY ----\\n\", 32))\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+/* Extract OpenSSH public key. */\n+static unsigned char *extract_openssh_pubkey(const unsigned char *file,\n+\tsize_t file_len, size_t *key_len)\n+{\n+\tconst unsigned char *base;\n+\tconst unsigned char *sep2;\n+\tsize_t blob_size;\n+\tbase = (const unsigned char*)strchr((const char*)file, ' ');\n+\tif (!base) {\n+\t\terror(\"Malformed OpenSSH pubkey file\");\n+\t\treturn NULL;\n+\t}\n+\tbase++;\n+\n+\tsep2 = (const unsigned char*)strchr((const char*)base, ' ');\n+\tif (!sep2)\n+\t\tblob_size = strlen((const char*)base);\n+\telse\n+\t\tblob_size = sep2 - base;\n+\n+\treturn decode_base64_chunk(base, blob_size, key_len);\n+}\n+\n+/* Is OpenSSH format key? */\n+static int is_openssh_pubkey(const unsigned char *file, size_t file_len)\n+{\n+\tif (file_len < 8)\n+\t\treturn 0;\n+\tif (!memcmp(file, \"ssh-rsa \", 8))\n+\t\treturn 1;\n+\tif (!memcmp(file, \"ssh-dss \", 8))\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+/*\n+ * Extract actual key blob from encoded key. The return value should be\n+ * freed. The reply has the component lengths present.\n+ */\n+unsigned char *extract_key_from_file(const unsigned char *file,\n+\tsize_t file_len, size_t *key_len)\n+{\n+\tif (is_openssh_pubkey(file, file_len))\n+\t\treturn extract_openssh_pubkey(file, file_len, key_len);\n+\tif (is_secsh_pubkey(file, file_len))\n+\t\treturn extract_secsh_pubkey(file, file_len, key_len);\n+\terror(\"Unknown public key file format\");\n+\treturn NULL;\n+}\n+\n+void dump_blob(const char *name, const unsigned char *blob, size_t bloblen)\n+{\n+\n+\tsize_t i;\n+\tfprintf(stderr, \"------- START %s (%u bytes) ------\\n\", name, (unsigned)bloblen);\n+\tfor(i = 0; i < bloblen; i++) {\n+\t\tfprintf(stderr, \"%02X \", blob[i]);\n+\t\tif (i % 16 == 15)\n+\t\t\tfprintf(stderr, \"\\n\");\n+\t}\n+\tif (i % 16)\n+\t\tfprintf(stderr, \"\\n\");\n+\tfprintf(stderr, \"-------- END %s -------\\n\", name);\n+\n+}\n+\n+int write_to_agent(int fd, const unsigned char *ptr, size_t size)\n+{\n+\twhile (size > 0) {\n+\t\tssize_t r;\n+\t\tr = write(fd, ptr, size);\n+\t\tif (r > 0) {\n+\t\t\tptr += r;\n+\t\t\tsize -= r;\n+\t\t} else if (r == 0) {\n+\t\t\terror(\"Connection to ssh-agent unexpectedly lost\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\telse if (errno == EINTR || errno == EAGAIN)\n+\t\t\tcontinue;\n+\t\telse {\n+\t\t\terror(\"Error writing to ssh-agent: %s\",\n+\t\t\t\tstrerror(errno));\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+int read_in_agent(int fd, unsigned char *ptr, size_t size)\n+{\n+\twhile (size > 0) {\n+\t\tssize_t r;\n+\t\tr = read(fd, ptr, size);\n+\t\tif (r > 0) {\n+\t\t\tptr += r;\n+\t\t\tsize -= r;\n+\t\t} else if (r == 0) {\n+\t\t\terror(\"Connection to ssh-agent unexpectedly lost\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\telse if (errno == EINTR || errno == EAGAIN)\n+\t\t\tcontinue;\n+\t\telse {\n+\t\t\terror(\"Error reading from ssh-agent: %s\",\n+\t\t\t\tstrerror(errno));\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+int write_packet_to_agent(int fd, const unsigned char *payload, size_t len)\n+{\n+\tunsigned char tmp[4];\n+\tencode_uint32(tmp, len);\n+\tif (write_to_agent(fd, tmp, 4) < 0)\n+\t\treturn -1;\n+\tif (write_to_agent(fd, payload, len) < 0)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+unsigned char *read_packet_from_agent(int fd, size_t *len)\n+{\n+\tunsigned char *ret;\n+\tunsigned char tmp[4];\n+\tif (read_in_agent(fd, tmp, 4) < 0)\n+\t\treturn NULL;\n+\t*len = decode_uint32(tmp);\n+\tif (*len > MAX_REPLY_SIZE) {\n+\t\terror(\"Reply from SSH agent too large (size %zu, maximum allowed %zu)\",\n+\t\t\t*len, (size_t)MAX_REPLY_SIZE);\n+\t\treturn NULL;\n+\t}\n+\tret = xmalloc(*len);\n+\tif (read_in_agent(fd, ret, *len) < 0) {\n+\t\tfree(ret);\n+\t\treturn NULL;\n+\t}\n+\treturn ret;\n+}\n+\n+static unsigned char *agent_do_io_cycle(int fd, const unsigned char *payload,\n+\tsize_t payload_len, size_t *reply_len)\n+{\n+\tif (write_packet_to_agent(fd, payload, payload_len) < 0)\n+\t\treturn NULL;\n+\treturn read_packet_from_agent(fd, reply_len);\n+}\n+\n+int ssh_agent_sock = -1;\n+\n+int init_ssh_agent()\n+{\n+\tconst char *agentpath;\n+\tstruct sockaddr_un addr;\n+\n+\tif (ssh_agent_sock >= 0)\n+\t\treturn 0;\n+\tif (ssh_agent_sock < -1)\n+\t\treturn -1;\n+\n+\tagentpath = getenv(\"SSH_AUTH_SOCK\");\n+\tif (!agentpath) {\n+\t\tssh_agent_sock = -2;\n+\t\treturn -1;\n+\t}\n+\n+\taddr.sun_family = AF_UNIX;\n+\tstrcpy(addr.sun_path, agentpath);\n+\n+\tssh_agent_sock = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (ssh_agent_sock < 0) {\n+\t\terror(\"Can't create socket: %s\",\n+\t\t\tstrerror(errno));\n+\t\tssh_agent_sock = -2;\n+\t\treturn -1;\n+\t}\n+\n+\tif (connect(ssh_agent_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {\n+\t\terror(\"Can't connect to ssh-agent(%s): %s\", agentpath,\n+\t\tstrerror(errno));\n+\t\tclose(ssh_agent_sock);\n+\t\tssh_agent_sock = -2;\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n+unsigned char *sign_using_ssh_agent(const char *keyname,\n+\tconst unsigned char *kblob, size_t kblob_size,\n+\tconst unsigned char *challenge, size_t challenge_size,\n+\tsize_t *signature_length, const char *type)\n+{\n+\tunsigned char *request;\n+\tunsigned char *reply;\n+\tsize_t reply_length;\n+\tsize_t request_length;\n+\tunsigned char *signblocks = NULL;\n+\n+\t/* 43 = 1 (type) + 8 (2 lengths) + 4 (flags) */\n+\trequest = xmalloc(13 + kblob_size + challenge_size);\n+\trequest_length = 13 + kblob_size + challenge_size;\n+\n+\trequest[0] = SSH2_AGENTC_SIGN_REQUEST;\n+\tencode_uint32(request + 1, kblob_size);\n+\tmemcpy(request + 5, kblob, kblob_size);\n+\tencode_uint32(request + 5 + kblob_size, challenge_size);\n+\tmemcpy(request + 9 + kblob_size, challenge, challenge_size);\n+\tencode_uint32(request + 9 + kblob_size + challenge_size, 0);\n+\n+\tif (init_ssh_agent() < 0)\n+\t\tgoto out_request;\n+\n+\treply = agent_do_io_cycle(ssh_agent_sock, request, request_length,\n+\t\t&reply_length);\n+\tif (!reply)\n+\t\tgoto out_request;\n+\n+\tsignblocks = extract_actual_key(reply, reply_length, type,\n+\t\tsignature_length);\n+\t/* extract_actual_key can fail, but we don't print any error for that. */\n+\n+\tfree(reply);\n+out_request:\n+\tfree(request);\n+\treturn signblocks;\n+}\n+\n+static const char *check_blob(const unsigned char* blob, size_t blob_size)\n+{\n+\tsize_t offset = 0;\n+\tunsigned round = 0;\n+\tunsigned rounds = 1;\n+\tconst char *type = NULL;\n+\n+\tfor (round = 0; round < rounds; round++) {\n+\t\tunsigned long len;\n+\t\tif (offset + 4 < offset || blob_size < offset + 4) {\n+\t\t\terror(\"Component length header incomplete (component=%u/%u, blob_size=%zu, offset=%zu\",\n+\t\t\t\tround + 1, rounds, blob_size, offset);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tlen = decode_uint32(blob + offset);\n+\t\tif (offset + len + 4 < offset + 4 || blob_size < offset + len + 4) {\n+\t\t\terror(\"Component incomplete (component=%u/%u, blob_size=%zu, offset=%zu, len=%lu)\",\n+\t\t\t\tround + 1, rounds, blob_size, offset, len);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tif (round == 0 && len == 7 && !memcmp(blob + offset + 4,\n+\t\t\t\"ssh-rsa\", 7)) {\n+\t\t\trounds = 3;\n+\t\t\ttype = \"ssh-rsa\";\n+\t\t} else if (round == 0 && len == 7 && !memcmp(blob + offset + 4,\n+\t\t\t\"ssh-dss\", 7)) {\n+\t\t\trounds = 5;\n+\t\t\ttype = \"ssh-dss\";\n+\t\t} else if (round == 0) {\n+\t\t\terror(\"Read key has unknown key type (neither ssh-rsa nor ssh-dss)\");\n+\t\t\treturn NULL;\n+\t\t}\n+\t\toffset += len + 4;\n+\t}\n+\tif (offset != blob_size) {\n+\t\terror(\"Garbage after end of key\");\n+\t\treturn NULL;\n+\t}\n+\treturn type;\n+}\n+\n+#define MAXPATH 4096\n+#define MAXKEY 65536\n+#define DATABLOCK 32\n+#define HASHALGO GCRY_MD_SHA256\n+#define HASHLEN 32\n+\n+void do_ssh_preauth(const char *ssh_user)\n+{\n+\tsend_ssh_authentication(NULL, ssh_user);\n+}\n+\n+void send_ssh_authentication(struct user *dispatcher, const char *ssh_user)\n+{\n+\tunsigned char *x = NULL;\n+\tsize_t i;\n+\tFILE* filp;\n+\tchar *path;\n+\tchar pathtemplate[MAXPATH];\n+\tunsigned char key[MAXKEY];\n+\tsize_t keysize;\n+\tunsigned char block[DATABLOCK] = {0};\n+\n+\tunsigned char *signature = NULL;\n+\tsize_t signature_length;\n+\tconst char *type;\n+\n+\tif (strlen(ssh_user) > MAXPATH - 32)\n+\t\tdie(\"Key name too long\");\n+\tsprintf(pathtemplate, \"~/.ssh/%s.pub\", ssh_user);\n+\tpath = expand_path(pathtemplate);\n+\n+\tif (!x) {\n+\t\t/* Read the pubkey file. */\n+\t\tfilp = fopen(path, \"r\");\n+\t\tif (!filp) {\n+\t\t\terror(\"Can't open '%s'\", path);\n+\t\t\tgoto out_readfile;\n+\t\t}\n+\t\tkeysize = fread(key, 1, MAXKEY, filp);\n+\t\tif (ferror(filp)) {\n+\t\t\terror(\"Can't read '%s'\", path);\n+\t\t\tgoto out_readfile;\n+\t\t}\n+\t\tif (!feof(filp)) {\n+\t\t\terror(\"Keyfile '%s' too large\", path);\n+\t\t\tgoto out_readfile;\n+\t\t}\n+\t\tfclose(filp);\n+\n+\t\tx = extract_key_from_file(key, keysize, &i);\n+\t}\n+out_readfile:\n+\tif (!x)\n+\t\tdie(\"Can't read any public key named '%s'\",\n+\t\t\tssh_user);\n+\n+\ttype = check_blob(x, i);\n+\tif (!type)\n+\t\tdie(\"Malformed public key for '%s'\", ssh_user);\n+\n+\tif (dispatcher) {\n+\t\tint s;\n+\t\tgnutls_session_t session;\n+\t\tunsigned char hash[HASHLEN];\n+\t\tconst gnutls_datum_t *cert;\n+\t\tunsigned int lsize;\n+\t\tsession = user_get_tls(dispatcher);\n+\t\tif (!session)\n+\t\t\tdie(\"SSH auth requires TLS\");\n+\t\tcert = gnutls_certificate_get_peers(session, &lsize);\n+\t\tif (!cert)\n+\t\t\tdie(\"Unable to get server certificate (server didn't send one?)\");\n+\n+\t\tgcry_md_hash_buffer(HASHALGO, hash, cert->data, cert->size);\n+\t\ts = gnutls_prf(session, 14, \"ssh-key-verify\", 0, HASHLEN,\n+\t\t\t(const char*)hash, DATABLOCK, (char*)block);\n+\t\tif (s < 0)\n+\t\t\tdie(\"Can't compute challenge: %s\",\n+\t\t\t\tgnutls_strerror(s));\n+\t}\n+\tif (!signature)\n+\t\tsignature = sign_using_ssh_agent(ssh_user,\n+\t\t\tx, i, block, DATABLOCK, &signature_length,\n+\t\t\ttype);\n+\tif (!signature)\n+\t\tsignature = sign_using_local_key(ssh_user,\n+\t\t\tx, i, block, DATABLOCK, &signature_length,\n+\t\t\ttype);\n+\tif (!signature)\n+\t\tdie(\"Can't sign using key '%s'\", ssh_user);\n+\n+\tif (dispatcher) {\n+\t\tstruct cbuffer* in;\n+\t\tchar tmp[5];\n+\t\tin = user_get_red_in(dispatcher);\n+\t\tsprintf(tmp, \"%04x\", (unsigned)(8 + i + signature_length));\n+\t\tcbuffer_write(in, (const unsigned char*)tmp, 4);\n+\t\tcbuffer_write(in, (const unsigned char*)\"ssh \", 4);\n+\t\tcbuffer_write(in, x, i);\n+\t\tcbuffer_write(in, signature, signature_length);\n+\t}\n+\n+\tfree(signature);\n+\tfree(x);\n+}\ndiff --git a/git-over-tls/ssh.h b/git-over-tls/ssh.h\nnew file mode 100644\nindex 0000000..3ee5daf\n--- /dev/null\n+++ b/git-over-tls/ssh.h\n@@ -0,0 +1,18 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 _ssh__h__included__\n+#define _ssh__h__included__\n+\n+#include \"user.h\"\n+\n+void do_ssh_preauth(const char *ssh_user);\n+void send_ssh_authentication(struct user *dispatcher, const char *ssh_user);\n+unsigned char *extract_key_from_file(const unsigned char *file,\n+\tsize_t file_len, size_t *key_len);\n+\n+#endif\ndiff --git a/git-over-tls/ssh_localkey.c b/git-over-tls/ssh_localkey.c\nnew file mode 100644\nindex 0000000..f27ee9b\n--- /dev/null\n+++ b/git-over-tls/ssh_localkey.c\n@@ -0,0 +1,435 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 <stdint.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include \"pem.h\"\n+#include \"base64.h\"\n+#include \"home.h\"\n+#include <gcrypt.h>\n+#ifdef USE_COMPAT_H\n+#include \"compat.h\"\n+#else\n+#include \"git-compat-util.h\"\n+#endif\n+\n+static int validate_key(const unsigned char *key, size_t keysize,\n+\tsize_t *offsets, size_t *lengths, int components)\n+{\n+\tsize_t offset = 0;\n+\tint c;\n+\n+\tfor(c = 0; c < components; c++) {\n+\t\tsize_t len = 0;\n+\t\tif (offset + 4 < offset || offset + 4 > keysize)\n+\t\t\treturn -1;\n+\t\toffsets[c] = offset + 4;\n+\t\tlen |= ((size_t)key[offset] << 24);\n+\t\tlen |= ((size_t)key[offset + 1] << 16);\n+\t\tlen |= ((size_t)key[offset + 2] << 8);\n+\t\tlen |= ((size_t)key[offset + 3]);\n+\t\tlengths[c] = len;\n+\t\tif (offset + 4 + len < offset + 4 ||\n+\t\t\toffset + 4 + len > keysize)\n+\t\t\treturn -1;\n+\t\toffset += (4 + len);\n+\t}\n+\tif (offset != keysize)\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+typedef void* voidptr;\n+\n+static unsigned char *sign_rsa(const unsigned char *key, size_t keysize,\n+\tconst unsigned char *challenge, size_t challenge_size,\n+\tsize_t *signature_size)\n+{\n+\tunsigned char *ret = NULL;\n+\tsize_t offsets[4];\n+\tsize_t lengths[4];\n+\tgcry_mpi_t rsa_e;\n+\tgcry_mpi_t rsa_n;\n+\tgcry_mpi_t rsa_d;\n+\tgcry_error_t err;\n+\tgcry_sexp_t _key;\n+\tgcry_sexp_t tosign;\n+\tgcry_sexp_t signature;\n+\tgcry_sexp_t sval;\n+\tgcry_sexp_t rsas_s;\n+\tconst unsigned char *rsa_s;\n+\tsize_t rsa_s_len;\n+\tint hashalgo = GCRY_MD_SHA256;\n+\tconst char *algo = \"sha256\";\n+\tint hashlen = 32;\n+\tunsigned char hashbuffer[128];\n+\tvoidptr params[3];\n+\tif (validate_key(key, keysize, offsets, lengths, 4) < 0) {\n+\t\terror(\"Bad RSA private key\");\n+\t\treturn NULL;\n+\t}\n+\terr = gcry_mpi_scan(&rsa_e, GCRYMPI_FMT_STD, key + offsets[1],\n+\t\tlengths[1], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read RSA e: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out;\n+\t}\n+\terr = gcry_mpi_scan(&rsa_n, GCRYMPI_FMT_STD, key + offsets[2],\n+\t\tlengths[2], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read RSA n: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_e;\n+\t}\n+\terr = gcry_mpi_scan(&rsa_d, GCRYMPI_FMT_STD, key + offsets[3],\n+\t\tlengths[3], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read RSA d: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_n;\n+\t}\n+\tparams[0] = &rsa_n;\n+\tparams[1] = &rsa_e;\n+\tparams[2] = &rsa_d;\n+\terr = gcry_sexp_build_array(&_key, NULL, \"(private-key (rsa (n %m) (e %m) (d %m)))\",\n+\t\tparams);\n+\tif (err) {\n+\t\terror(\"Can't build key: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_d;\n+\t}\n+\n+\tgcry_md_hash_buffer(hashalgo, hashbuffer, challenge,\n+\t\tchallenge_size);\n+\n+\terr = gcry_sexp_build(&tosign, NULL, \"(data (flags pkcs1) (hash %s %b))\",\n+\t\talgo, hashlen, hashbuffer);\n+\tif (err) {\n+\t\terror(\"Can't build data to sign: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_key;\n+\t}\n+\n+\terr = gcry_pk_sign(&signature, tosign, _key);\n+\tif (err) {\n+\t\terror(\"Can't sign data: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_tosign;\n+\t}\n+\n+\tsval = gcry_sexp_nth(signature, 1);\n+\tif(!sval) {\n+\t\terror(\"DSA signature output faulty\");\n+\t\tgoto out_signature;\n+\t}\n+\trsas_s = gcry_sexp_nth(sval, 1);\n+\tif(!rsas_s) {\n+\t\terror(\"RSA signature output faulty (no s)\");\n+\t\tgoto out_sval;\n+\t}\n+\trsa_s = (const unsigned char*)gcry_sexp_nth_data(rsas_s, 1, &rsa_s_len);\n+\tif(!rsa_s) {\n+\t\terror(\"RSA signature output faulty (no real s)\");\n+\t\tgoto out_s;\n+\t}\n+\n+\t*signature_size = 4 + rsa_s_len;\n+\tret = xmalloc(*signature_size);\n+\tencode_uint32(ret, rsa_s_len);\n+\tmemcpy(ret + 4, rsa_s, rsa_s_len);\n+\n+out_s:\n+\tgcry_sexp_release(rsas_s);\n+out_sval:\n+\tgcry_sexp_release(sval);\n+out_signature:\n+\tgcry_sexp_release(signature);\n+out_tosign:\n+\tgcry_sexp_release(tosign);\n+out_key:\n+\tgcry_sexp_release(_key);\n+out_d:\n+\tgcry_mpi_release(rsa_d);\n+out_n:\n+\tgcry_mpi_release(rsa_n);\n+out_e:\n+\tgcry_mpi_release(rsa_e);\n+out:\n+\treturn ret;\n+}\n+\n+static unsigned char *sign_dsa(const unsigned char *key, size_t keysize,\n+\tconst unsigned char *challenge, size_t challenge_size,\n+\tsize_t *signature_size)\n+{\n+\tunsigned char *ret = NULL;\n+\tsize_t offsets[6];\n+\tsize_t lengths[6];\n+\tgcry_mpi_t dsa_p;\n+\tgcry_mpi_t dsa_q;\n+\tgcry_mpi_t dsa_g;\n+\tgcry_mpi_t dsa_y;\n+\tgcry_mpi_t dsa_x;\n+\tgcry_mpi_t dsa_e;\n+\tint hashalgo;\n+\tconst char *algo;\n+\tint hashlen;\n+\tunsigned char hashbuffer[128];\n+\tvoidptr params[5];\n+\tgcry_sexp_t _key;\n+\tgcry_sexp_t tosign;\n+\tgcry_sexp_t signature;\n+\tgcry_sexp_t sval;\n+\tgcry_sexp_t dsas_r;\n+\tgcry_sexp_t dsas_s;\n+\tconst unsigned char *dsa_r;\n+\tsize_t dsa_r_len;\n+\tconst unsigned char *dsa_s;\n+\tsize_t dsa_s_len;\n+\tgcry_error_t err;\n+\tint sbits;\n+\n+\tif (validate_key(key, keysize, offsets, lengths, 6) < 0) {\n+\t\terror(\"Bad DSA private key\");\n+\t\treturn NULL;\n+\t}\n+\n+\terr = gcry_mpi_scan(&dsa_p, GCRYMPI_FMT_STD, key + offsets[1],\n+\t\tlengths[1], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA p: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out;\n+\t}\n+\terr = gcry_mpi_scan(&dsa_q, GCRYMPI_FMT_STD, key + offsets[2],\n+\t\tlengths[2], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA q: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_p;\n+\t}\n+\terr = gcry_mpi_scan(&dsa_g, GCRYMPI_FMT_STD, key + offsets[3],\n+\t\tlengths[3], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA g: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_q;\n+\t}\n+\terr = gcry_mpi_scan(&dsa_y, GCRYMPI_FMT_STD, key + offsets[4],\n+\t\tlengths[4], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA y: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_g;\n+\t}\n+\terr = gcry_mpi_scan(&dsa_x, GCRYMPI_FMT_STD, key + offsets[5],\n+\t\tlengths[5], NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA x: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_y;\n+\t}\n+\n+\tparams[0] = &dsa_p;\n+\tparams[1] = &dsa_q;\n+\tparams[2] = &dsa_g;\n+\tparams[3] = &dsa_y;\n+\tparams[4] = &dsa_x;\n+\terr = gcry_sexp_build_array(&_key, NULL, \"(private-key (dsa (p %m) (q %m) (g %m) (y %m) (x %m)))\",\n+\t\tparams);\n+\tif (err) {\n+\t\terror(\"Can't build key: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_x;\n+\t}\n+\n+\tsbits = gcry_mpi_get_nbits(dsa_q);\n+\tif(sbits <= 160) {\n+\t\talgo = \"sha1\";\n+\t\thashalgo = GCRY_MD_SHA1;\n+\t\thashlen = 20;\n+\t} else if(sbits <= 224) {\n+\t\talgo = \"sha224\";\n+\t\thashalgo = GCRY_MD_SHA224;\n+\t\thashlen = 28;\n+\t} else if(sbits <= 256) {\n+\t\talgo = \"sha256\";\n+\t\thashalgo = GCRY_MD_SHA256;\n+\t\thashlen = 32;\n+\t} else if(sbits <= 384) {\n+\t\talgo = \"sha384\";\n+\t\thashalgo = GCRY_MD_SHA384;\n+\t\thashlen = 48;\n+\t} else if(sbits <= 512) {\n+\t\talgo = \"sha512\";\n+\t\thashalgo = GCRY_MD_SHA512;\n+\t\thashlen = 64;\n+\t} else {\n+\t\terror(\"DSA parameter q out of range (2^512 or larger)\");\n+\t\tgoto out_key;\n+\t}\n+\n+\tgcry_md_hash_buffer(hashalgo, hashbuffer, challenge,\n+\t\tchallenge_size);\n+\terr = gcry_mpi_scan(&dsa_e, GCRYMPI_FMT_USG, hashbuffer,\n+\t\thashlen, NULL);\n+\tif (err) {\n+\t\terror(\"Can't read DSA e: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_key;\n+\t}\n+\n+\terr = gcry_sexp_build(&tosign, NULL, \"(%m)\",\n+\t\tdsa_e);\n+\tif (err) {\n+\t\terror(\"Can't build data to sign: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_e;\n+\t}\n+\n+\terr = gcry_pk_sign(&signature, tosign, _key);\n+\tif (err) {\n+\t\terror(\"Can't sign data: %s/%s\", gcry_strsource(err),\n+\t\t\tgcry_strerror(err));\n+\t\tgoto out_tosign;\n+\t}\n+\n+\tsval = gcry_sexp_nth(signature, 1);\n+\tif(!sval) {\n+\t\terror(\"DSA signature output faulty (no sval)\");\n+\t\tgoto out_signature;\n+\t}\n+\tdsas_r = gcry_sexp_nth(sval, 1);\n+\tif(!dsas_r) {\n+\t\terror(\"DSA signature output faulty (no r)\");\n+\t\tgoto out_sval;\n+\t}\n+\tdsa_r = (const unsigned char*)gcry_sexp_nth_data(dsas_r, 1, &dsa_r_len);\n+\tif(!dsa_r) {\n+\t\terror(\"DSA signature output faulty (no real r)\");\n+\t\tgoto out_r;\n+\t}\n+\tdsas_s = gcry_sexp_nth(sval, 2);\n+\tif(!dsas_s) {\n+\t\terror(\"DSA signature output faulty (no s)\");\n+\t\tgoto out_r;\n+\t}\n+\tdsa_s = (const unsigned char*)gcry_sexp_nth_data(dsas_s, 1, &dsa_s_len);\n+\tif(!dsa_s) {\n+\t\terror(\"DSA signature output faulty (no real s)\");\n+\t\tgoto out_s;\n+\t}\n+\n+\t*signature_size = 8 + dsa_r_len + dsa_s_len;\n+\tret = xmalloc(*signature_size);\n+\tencode_uint32(ret, dsa_r_len);\n+\tmemcpy(ret + 4, dsa_r, dsa_r_len);\n+\tencode_uint32(ret + 4 + dsa_r_len, dsa_s_len);\n+\tmemcpy(ret + 8 + dsa_r_len, dsa_s, dsa_s_len);\n+\n+out_s:\n+\tgcry_sexp_release(dsas_s);\n+out_r:\n+\tgcry_sexp_release(dsas_r);\n+out_sval:\n+\tgcry_sexp_release(sval);\n+out_signature:\n+\tgcry_sexp_release(signature);\n+out_tosign:\n+\tgcry_sexp_release(tosign);\n+out_e:\n+\tgcry_mpi_release(dsa_e);\n+out_key:\n+\tgcry_sexp_release(_key);\n+out_x:\n+\tgcry_mpi_release(dsa_x);\n+out_y:\n+\tgcry_mpi_release(dsa_y);\n+out_g:\n+\tgcry_mpi_release(dsa_g);\n+out_q:\n+\tgcry_mpi_release(dsa_q);\n+out_p:\n+\tgcry_mpi_release(dsa_p);\n+out:\n+\treturn ret;\n+}\n+\n+#define MAXPATH 4096\n+#define MAXKEY 65536\n+\n+unsigned char *sign_using_local_key(const char *keyname,\n+\tconst unsigned char *kblob, size_t kblob_size,\n+\tconst unsigned char *challenge, size_t challenge_size,\n+\tsize_t *signature_length, const char *type)\n+{\n+\tFILE* filp;\n+\tunsigned char *ret = NULL;\n+\n+\tchar *path;\n+\tchar pathtemplate[MAXPATH];\n+\tunsigned char key[MAXKEY];\n+\tsize_t keysize;\n+\tint readkey_flag = 0;\n+\n+\tunsigned char *x;\n+\tsize_t i;\n+\n+\tif (strlen(keyname) > MAXPATH - 32)\n+\t\tdie(\"Key name too long\");\n+\tsprintf(pathtemplate, \"~/.ssh/%s\", keyname);\n+\tpath = expand_path(pathtemplate);\n+\n+\t/* Read the privkey file. */\n+\tfilp = fopen(path, \"r\");\n+\tif (!filp) {\n+\t\terror(\"Can't open '%s'\", path);\n+\t\tgoto out_readfile;\n+\t}\n+\tkeysize = fread(key, 1, MAXKEY, filp);\n+\tif (ferror(filp)) {\n+\t\terror(\"Can't read '%s'\", path);\n+\t\tgoto out_readfile_handle;\n+\t}\n+\tif (!feof(filp)) {\n+\t\terror(\"Keyfile '%s' too large\", path);\n+\t\tgoto out_readfile_handle;\n+\t}\n+\treadkey_flag = 1;\n+out_readfile_handle:\n+\tfclose(filp);\n+out_readfile:\n+\tfree(path);\n+\tif (!readkey_flag) {\n+\t\terror(\"Can't read any private key named '%s'\", keyname);\n+\t\treturn NULL;\n+\t}\n+\n+\tx = pem_decode(key, keysize, keyname, &i);\n+\tif (!x) {\n+\t\terror(\"Can't decode key '%s'\", keyname);\n+\t\treturn NULL;\n+\t}\n+\tif (kblob_size > i || memcmp(kblob, x, kblob_size)) {\n+\t\tfree(x);\n+\t\terror(\"Pubic and private keys for '%s' are inconsistent\",\n+\t\t\tkeyname);\n+\t\treturn NULL;\n+\t}\n+\tif (!strcmp(type, \"ssh-rsa\"))\n+\t\tret = sign_rsa(x, i, challenge, challenge_size,\n+\t\t\tsignature_length);\n+\telse if (!strcmp(type, \"ssh-dss\"))\n+\t\tret = sign_dsa(x, i, challenge, challenge_size,\n+\t\t\tsignature_length);\n+\telse\n+\t\terror(\"Key '%s' is of unknown type\", keyname);\n+\tfree(x);\n+\treturn ret;\n+}\ndiff --git a/git-over-tls/ssh_localkey.h b/git-over-tls/ssh_localkey.h\nnew file mode 100644\nindex 0000000..223b97f\n--- /dev/null\n+++ b/git-over-tls/ssh_localkey.h\n@@ -0,0 +1,18 @@\n+/*\n+ * Copyright (C) Ilari Liusvaara 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 _ssh_localkey__h__include__\n+#define _ssh_localkey__h__include__\n+\n+#include <stdlib.h>\n+\n+unsigned char *sign_using_local_key(const char *keyname,\n+\tconst unsigned char *kblob, size_t kblob_size,\n+\tconst unsigned char *challenge, size_t challenge_size,\n+\tsize_t *signature_length, const char *type);\n+\n+#endif\n-- \n1.7.1.rc2.10.g714149\n"},{"id":"140689","messageId":"o2tfabb9a1e1005011017heb74f177qf5b16eabedddce2f@mail.gmail.com","threadId":"23654","inReplyTo":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [PATCH 0/3] git over TLS (gits://) support","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-05-01T17:17:05Z","receivedAt":"2010-05-01T17:17:05Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Sat, May 1, 2010 at 19:09, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n>  40 files changed, 7913 insertions(+), 3 deletions(-)\n\nHow do you suppose anyone can ever review that much code? I don't\nreally see anyone having the time to thoroughly review 8kloc?\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"140819","messageId":"4BDE9AE4.9010908@op5.se","threadId":"23654","inReplyTo":"1272733791-11341-3-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [PATCH 2/3] git over TLS (gits://) support (part 2)","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2010-05-03T09:44:04Z","receivedAt":"2010-05-03T09:44:04Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"On 05/01/2010 07:09 PM, Ilari Liusvaara wrote:\n\nA monster-patch, if ever I saw one, yet without a sensible commit message.\nWhat is it this patch actually does?\n\n> +#include \"base64.h\"\n> +#ifdef USE_COMPAT_H\n> +#include \"compat.h\"\n> +#else\n> +#include \"git-compat-util.h\"\n> +#endif\n> +\n\nThis would be better moved to compat.h, so the entire file is excluded\nand git-compat-util.h is included instead if we're supposed to use that\none. That way you won't have to remember this in every file you use.\n\n> +void encode_uint32(unsigned char *ptr, unsigned long value)\n> +{\n> +\tptr[0] = (unsigned char)(value>>  24);\n> +\tptr[1] = (unsigned char)(value>>  16);\n> +\tptr[2] = (unsigned char)(value>>  8);\n> +\tptr[3] = (unsigned char)(value);\n> +}\n> +\n\nThe second parameter should be uint32_t, obviously.\n\nReviewing the rest of the patch will have to wait until there's a\nproper commit message describing what it's supposed to do, as I\nhave nowhere near enough time to try to analyze 100k worth of\ncode to figure out why it's necessary to add it to git.git.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"140828","messageId":"j2tbe6fef0d1005030557l1f746349zfce18213add19efe@mail.gmail.com","threadId":"23654","inReplyTo":"1272733791-11341-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [PATCH 0/3] git over TLS (gits://) support","fromName":"Tay Ray Chuan","fromEmail":"rctay89@gmail.com","sentAt":"2010-05-03T12:57:03Z","receivedAt":"2010-05-03T12:57:03Z","isPatch":true,"sender":{"key":"rctay89@gmail.com","avatar":"https://avatars.githubusercontent.com/u/61553?v=4"},"body":"Hi,\n\nOn Sun, May 2, 2010 at 1:09 AM, Ilari Liusvaara\n<ilari.liusvaara@elisanet.fi> wrote:\n> This is newer version of gits:// client side support.\n\nin case anyone is wondering where the older version is, Ilari posted\nit early this year:\n\n  http://thread.gmane.org/gmane.comp.version-control.git/136816\n\n>From the discussion, Avery (Cc'ed) had some strong objections; the\npoint that I remembered the most clearly was \"why not just use SSH\".\nThere didn't seem to be any headway.\n\nI hope that we can continue where we left off and resolve the various\nobjections without too much duplication of shot-down arguments.\n\n-- \nCheers,\nRay Chuan\n"},{"id":"140830","messageId":"4BDECEFF.7060900@drmicha.warpmail.net","threadId":"23654","inReplyTo":"j2tbe6fef0d1005030557l1f746349zfce18213add19efe@mail.gmail.com","subject":"Re: [PATCH 0/3] git over TLS (gits://) support","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2010-05-03T13:26:23Z","receivedAt":"2010-05-03T13:26:23Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Tay Ray Chuan venit, vidit, dixit 03.05.2010 14:57:\n> Hi,\n> \n> On Sun, May 2, 2010 at 1:09 AM, Ilari Liusvaara\n> <ilari.liusvaara@elisanet.fi> wrote:\n>> This is newer version of gits:// client side support.\n> \n> in case anyone is wondering where the older version is, Ilari posted\n> it early this year:\n> \n>   http://thread.gmane.org/gmane.comp.version-control.git/136816\n> \n> From the discussion, Avery (Cc'ed) had some strong objections; the\n> point that I remembered the most clearly was \"why not just use SSH\".\n> There didn't seem to be any headway.\n> \n> I hope that we can continue where we left off and resolve the various\n> objections without too much duplication of shot-down arguments.\n> \n\nExcept for \"This is a newer patch\" and the split in 3 rather than 2,\nthere is no information whatsoever on what's different and which\nquestions have been addressed. Given that, it would be only fair to\nraise the same objections again.\n\nCondensing the old thread to a bare minimum, these questions seem to be\nopen (though answered, one might say):\n\n- What ssh+git: problems does gits: address?\n- What tells us these are solved by gits:?\n- What tells us there are no gits: problems which ssh+git: would solve?\n\nFrankly, so far I see no tests, no benchmarks, one author convinced by\nhis own code, and seemingly nobody willing to really look a the patches.\n\nMichael\n"},{"id":"140838","messageId":"s2h32541b131005031058o4d2df47ei7fcd5205db89eeb7@mail.gmail.com","threadId":"23654","inReplyTo":"j2tbe6fef0d1005030557l1f746349zfce18213add19efe@mail.gmail.com","subject":"Re: [PATCH 0/3] git over TLS (gits://) support","fromName":"Avery Pennarun","fromEmail":"apenwarr@gmail.com","sentAt":"2010-05-03T17:58:05Z","receivedAt":"2010-05-03T17:58:05Z","isPatch":true,"sender":{"key":"apenwarr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/20592?v=4"},"body":"On Mon, May 3, 2010 at 8:57 AM, Tay Ray Chuan <rctay89@gmail.com> wrote:\n> From the discussion, Avery (Cc'ed) had some strong objections; the\n> point that I remembered the most clearly was \"why not just use SSH\".\n> There didn't seem to be any headway.\n>\n> I hope that we can continue where we left off and resolve the various\n> objections without too much duplication of shot-down arguments.\n\nAfter more consideration, I withdraw my objection since the fact that\nI don't need it, nor does anybody I know need it, isn't actually a\ngood argument against including it.  I assume the Powers That Be would\nstill like to see some people who *do* need it before including such a\nhuge chunk of code.\n\nHave fun,\n\nAvery\n"}]}