* [RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2)
2010-01-13 13:19 [RFC 0/2] Git-over-TLS (gits://) client side support Ilari Liusvaara
@ 2010-01-13 13:19 ` Ilari Liusvaara
2010-01-13 13:19 ` [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 " Ilari Liusvaara
` (2 subsequent siblings)
3 siblings, 0 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
To: git
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
Makefile | 23 ++-
git-over-tls/.gitignore | 5 +
git-over-tls/Makefile | 46 +++
git-over-tls/cbuffer.c | 504 ++++++++++++++++++++++++++++++++
git-over-tls/cbuffer.h | 304 +++++++++++++++++++
git-over-tls/certificate.c | 306 +++++++++++++++++++
git-over-tls/certificate.h | 28 ++
git-over-tls/connect.c | 263 +++++++++++++++++
git-over-tls/connect.h | 14 +
git-over-tls/genkeypair.c | 38 +++
git-over-tls/gensrpverifier.c | 372 +++++++++++++++++++++++
git-over-tls/getkeyid.c | 118 ++++++++
git-over-tls/gits-send-special-command | 22 ++
git-over-tls/home.c | 47 +++
git-over-tls/home.h | 13 +
git-over-tls/hostkey.c | 116 ++++++++
git-over-tls/hostkey.h | 15 +
git-over-tls/hostkeymanager.c | 305 +++++++++++++++++++
git-over-tls/keypairs.c | 60 ++++
git-over-tls/keypairs.h | 16 +
20 files changed, 2613 insertions(+), 2 deletions(-)
create mode 100644 git-over-tls/.gitignore
create mode 100644 git-over-tls/Makefile
create mode 100644 git-over-tls/cbuffer.c
create mode 100644 git-over-tls/cbuffer.h
create mode 100644 git-over-tls/certificate.c
create mode 100644 git-over-tls/certificate.h
create mode 100644 git-over-tls/connect.c
create mode 100644 git-over-tls/connect.h
create mode 100644 git-over-tls/genkeypair.c
create mode 100644 git-over-tls/gensrpverifier.c
create mode 100644 git-over-tls/getkeyid.c
create mode 100755 git-over-tls/gits-send-special-command
create mode 100644 git-over-tls/home.c
create mode 100644 git-over-tls/home.h
create mode 100644 git-over-tls/hostkey.c
create mode 100644 git-over-tls/hostkey.h
create mode 100644 git-over-tls/hostkeymanager.c
create mode 100644 git-over-tls/keypairs.c
create mode 100644 git-over-tls/keypairs.h
diff --git a/Makefile b/Makefile
index 4d2b99c..81cc848 100644
--- a/Makefile
+++ b/Makefile
@@ -163,6 +163,10 @@ all::
# apostrophes to be ASCII so that cut&pasting examples to the shell
# will work.
#
+# Define NO_GNUTLS if you don't want to use GnuTLS.
+#
+# Define NO_SRP if you don't want SRP support.
+#
# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
# MakeMaker (e.g. using ActiveState under Cygwin).
#
@@ -171,7 +175,6 @@ all::
# Define NO_PYTHON if you do not want Python scripts or libraries at all.
#
# Define NO_TCLTK if you do not want Tcl/Tk GUI.
-#
# The TCL_PATH variable governs the location of the Tcl interpreter
# used to optimize git-gui for your system. Only used if NO_TCLTK
# is not set. Defaults to the bare 'tclsh'.
@@ -291,7 +294,7 @@ TCL_PATH = tclsh
TCLTK_PATH = wish
PTHREAD_LIBS = -lpthread
-export TCL_PATH TCLTK_PATH
+export TCL_PATH TCLTK_PATH CC
# sparse is architecture-neutral, which means that we need to tell it
# explicitly what architecture to check for. Fix this up for yours..
@@ -1248,6 +1251,9 @@ endif
ifdef NO_IPV6
BASIC_CFLAGS += -DNO_IPV6
endif
+ifdef NO_SRP
+ BASIC_CFLAGS += -DDISABLE_SRP
+endif
ifdef NO_UINTMAX_T
BASIC_CFLAGS += -Duintmax_t=uint32_t
endif
@@ -1399,6 +1405,10 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
LIBS = $(GITLIBS) $(EXTLIBS)
+# In case some subdirectory wants to use git API libs.
+GIT_PROGRAM_LINKLIBS = $(LIBS)
+export GIT_PROGRAM_LINKLIBS
+
BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
$(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
@@ -1442,6 +1452,9 @@ endif
ifndef NO_PERL
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
endif
+ifndef NO_GNUTLS
+ $(QUIET_SUBDIR0)git-over-tls $(QUIET_SUBDIR1) prefix='$(prefix_SQ)' all
+endif
ifndef NO_PYTHON
$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
endif
@@ -1825,6 +1838,9 @@ install: all
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ifndef NO_GNUTLS
+ $(MAKE) -C git-over-tls DESTDIR_BIN='$(DESTDIR_SQ)$(bindir_SQ)' DESTDIR_GITEXEC='$(DESTDIR_SQ)$(gitexec_instdir_SQ)' install
+endif
ifndef NO_PERL
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
endif
@@ -1956,6 +1972,9 @@ ifndef NO_PERL
$(RM) gitweb/gitweb.cgi
$(MAKE) -C perl clean
endif
+ifndef NO_GNUTLS
+ $(MAKE) -C git-over-tls clean
+endif
ifndef NO_PYTHON
$(MAKE) -C git_remote_helpers clean
endif
diff --git a/git-over-tls/.gitignore b/git-over-tls/.gitignore
new file mode 100644
index 0000000..546e49c
--- /dev/null
+++ b/git-over-tls/.gitignore
@@ -0,0 +1,5 @@
+/git-remote-gits
+/gits-get-key-id
+/gits-generate-keypair
+/gits-generate-srp-verifier
+/gits-hostkey
diff --git a/git-over-tls/Makefile b/git-over-tls/Makefile
new file mode 100644
index 0000000..7804ec7
--- /dev/null
+++ b/git-over-tls/Makefile
@@ -0,0 +1,46 @@
+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))
+helper = git-remote-gits
+programs = gits-get-key-id gits-hostkey gits-generate-keypair
+scripts = gits-send-special-command
+flags=
+
+# These can't be inherited from upper level or they won't work right...
+ifndef V
+ QUIET_CC = @echo ' ' CC $@;
+ QUIET_LINK = @echo ' ' LINK $@;
+endif
+
+ifdef NO_SRP
+flags += -DDISABLE_SRP
+else
+programs += gits-generate-srp-verifier
+endif
+
+all: $(programs) $(helper)
+
+git-remote-gits: main.o user.o cbuffer.o srp_askpass.o keypairs.o hostkey.o home.o certificate.o prompt.o misc.o prompt.o connect.o
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-get-key-id: getkeyid.o certificate.o cbuffer.o home.o
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+ifndef NO_SRP
+gits-generate-srp-verifier: gensrpverifier.o prompt.o
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+endif
+
+gits-hostkey: hostkeymanager.o home.o
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-generate-keypair: genkeypair.o home.o mkcert.o cbuffer.o prompt.o
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+%.o: %.c
+ $(QUIET_CC)$(CC) $(CLFAGS) -c -o $@ $< $(flags) -I..
+
+install: all
+ $(INSTALL) $(programs) $(scripts) '$(DESTDIR_BIN)'
+ $(INSTALL) $(helper) '$(DESTDIR_GITEXEC)'
+
+clean:
+ $(RM) -f *.o $(programs) $(helper)
diff --git a/git-over-tls/cbuffer.c b/git-over-tls/cbuffer.c
new file mode 100644
index 0000000..e2adec7
--- /dev/null
+++ b/git-over-tls/cbuffer.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "cbuffer.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+#include <sys/uio.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/*
+ * NOTE: This code may not crash, call exit or anything similar unless
+ * program state is corrupt or call parameters are completely invalid.
+ * It also may not call Git APIs.
+ */
+
+struct cbuffer
+{
+ /* Base address for data area. */
+ unsigned char *cb_base;
+ /* Amount of free space. */
+ size_t cb_free;
+ /* Amount of used space. */
+ size_t cb_used;
+ /* Get pointer (relative to base) */
+ size_t cb_get;
+ /* Put pointer (relative to base) */
+ size_t cb_put;
+ /* Total size */
+ size_t cb_size;
+};
+
+/* Assert that invariants of circular buffer hold. */
+static void assert_invariants(struct cbuffer *cbuf)
+{
+ assert(cbuf->cb_free >= 0 && cbuf->cb_free <= cbuf->cb_size);
+ assert(cbuf->cb_used >= 0 && cbuf->cb_used <= cbuf->cb_size);
+ assert(cbuf->cb_free + cbuf->cb_used == cbuf->cb_size);
+ assert(cbuf->cb_get >= 0 && cbuf->cb_get <= cbuf->cb_size);
+ assert(cbuf->cb_put >= 0 && cbuf->cb_put <= cbuf->cb_size);
+ if (cbuf->cb_get == cbuf->cb_put)
+ assert(cbuf->cb_free == 0 || cbuf->cb_used == 0);
+ else if (cbuf->cb_get < cbuf->cb_put)
+ assert(cbuf->cb_get + cbuf->cb_used == cbuf->cb_put);
+ else
+ assert(cbuf->cb_put + cbuf->cb_free == cbuf->cb_get);
+}
+
+/* Ack specified amount of data written. */
+static void ack_write(struct cbuffer *cbuf, size_t wsize)
+{
+ assert_invariants(cbuf);
+ assert(wsize <= cbuf->cb_free);
+ /*
+ * Writing data decreses free space, increases used space,
+ * increases put pointer, but it will wrap around if it
+ * goes past end of buffer.
+ */
+ cbuf->cb_free -= wsize;
+ cbuf->cb_used += wsize;
+ cbuf->cb_put += wsize;
+ if (cbuf->cb_put >= cbuf->cb_size)
+ cbuf->cb_put -= cbuf->cb_size;
+ assert_invariants(cbuf);
+}
+
+/* Ack specified amount of data read (removing it). */
+static void ack_read(struct cbuffer *cbuf, size_t rsize)
+{
+ assert_invariants(cbuf);
+ assert(rsize <= cbuf->cb_used);
+ /*
+ * Reading data decreses used space, increases free space,
+ * increases get pointer, but it will wrap around if it
+ * goes past end of buffer.
+ */
+ cbuf->cb_free += rsize;
+ cbuf->cb_used -= rsize;
+ cbuf->cb_get += rsize;
+ if (cbuf->cb_get >= cbuf->cb_size)
+ cbuf->cb_get -= cbuf->cb_size;
+ assert_invariants(cbuf);
+}
+
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize)
+{
+ struct cbuffer *cbuf = (struct cbuffer*)malloc(sizeof(
+ struct cbuffer));
+ if (cbuf) {
+ cbuf->cb_base = data;
+ cbuf->cb_size = cbuf->cb_free = datasize;
+ cbuf->cb_used = cbuf->cb_get = cbuf->cb_put = 0;
+ assert_invariants(cbuf);
+ }
+ return cbuf;
+}
+
+void cbuffer_destroy(struct cbuffer *cbuf)
+{
+ if (cbuf) {
+ assert_invariants(cbuf);
+ free(cbuf);
+ }
+}
+
+size_t cbuffer_used(struct cbuffer *cbuf)
+{
+ assert_invariants(cbuf);
+ return cbuf->cb_used;
+}
+
+size_t cbuffer_free(struct cbuffer *cbuf)
+{
+ assert_invariants(cbuf);
+ return cbuf->cb_free;
+}
+
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+ assert_invariants(cbuf);
+
+ /* Check that user doesn't try to read too much. */
+ if (toread > cbuf->cb_used)
+ return -1;
+
+ size_t tocopy = toread;
+
+ /*
+ * Limit the amount of data read to amount fits inside single
+ * segment. If get pointer is not greater or equal to put pointer,
+ * then entiere used space is only single segment.
+ */
+ if (cbuf->cb_get >= cbuf->cb_put)
+ if (tocopy > cbuf->cb_size - cbuf->cb_get)
+ tocopy = cbuf->cb_size - cbuf->cb_get;
+
+ if (tocopy > 0) {
+ /* Copy the segment and mark it read. */
+ memcpy(dest, cbuf->cb_base + cbuf->cb_get, tocopy);
+ ack_read(cbuf, tocopy);
+ /* Adjust the request for subsequent segments. */
+ toread -= tocopy;
+ dest += tocopy;
+ }
+
+ /*
+ * If the read was incomplete, repeat the read request for the
+ * non-read tail.
+ */
+ if (toread > 0)
+ return cbuffer_read(cbuf, dest, toread);
+
+ assert_invariants(cbuf);
+
+ return 0;
+}
+
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+ assert_invariants(cbuf);
+
+ /* Check that user doesn't try to peek too much. */
+ if (toread > cbuf->cb_used)
+ return -1;
+
+ /* Is the used space as single segment or in two segments? */
+ if (cbuf->cb_get + toread > cbuf->cb_size) {
+ /* Two. We have to compute where segment boundary is and
+ copy the data as two copies. */
+ size_t firstseg = cbuf->cb_size - cbuf->cb_get;
+ memcpy(dest, cbuf->cb_base + cbuf->cb_get, firstseg);
+ memcpy(dest + firstseg, cbuf->cb_base, toread - firstseg);
+ } else {
+ /* One, data can be read as single copy. */
+ memcpy(dest, cbuf->cb_base + cbuf->cb_get, toread);
+ }
+
+ assert_invariants(cbuf);
+
+ return 0;
+}
+
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+ size_t towrite)
+{
+ assert_invariants(cbuf);
+
+ /* Check that user doesn't try to write too much. */
+ if (towrite > cbuf->cb_free)
+ return -1;
+
+ size_t tocopy = towrite;
+
+ /*
+ * Limit the amount of data written to amount fits inside single
+ * segment. If put pointer is not greater or equal to get pointer,
+ * then entiere free space is only single segment.
+ */
+ if (cbuf->cb_put >= cbuf->cb_get)
+ if (tocopy > cbuf->cb_size - cbuf->cb_put)
+ tocopy = cbuf->cb_size - cbuf->cb_put;
+
+ if (tocopy > 0) {
+ /* Copy the segment and mark it written. */
+ memcpy(cbuf->cb_base + cbuf->cb_put, src, tocopy);
+ ack_write(cbuf, tocopy);
+ /* Adjust the request for subsequent segments. */
+ towrite -= tocopy;
+ src += tocopy;
+ }
+
+ /*
+ * If the write was incomplete, repeat the write request for the
+ * non-written tail.
+ */
+ if (towrite > 0)
+ return cbuffer_write(cbuf, src, towrite);
+
+ assert_invariants(cbuf);
+
+ return 0;
+}
+
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove)
+{
+ assert_invariants(dest);
+ assert_invariants(src);
+
+ /* Check that amount to move isn't too great. */
+ if (tomove > dest->cb_free)
+ return -1;
+ if (tomove > src->cb_used)
+ return -1;
+
+ size_t tocopy = tomove;
+ /*
+ * Compute maximum number of bytes that is less than amount to
+ * move and amount of used/free space in current segments in
+ * both buffers.
+ */
+ if (dest->cb_put >= dest->cb_get)
+ if (tocopy > dest->cb_size - dest->cb_put)
+ tocopy = dest->cb_size - dest->cb_put;
+ if (src->cb_get >= src->cb_put)
+ if (tocopy > src->cb_size - src->cb_get)
+ tocopy = src->cb_size - src->cb_get;
+
+ if (tocopy > 0) {
+ /* Move the segment. and mark it moved. */
+ memcpy(dest->cb_base + dest->cb_put,
+ src->cb_base +src->cb_get, tocopy);
+ ack_read(src, tocopy);
+ ack_write(dest, tocopy);
+ /* Adjust request for subsequent segments. */
+ tomove -= tocopy;
+ }
+
+ /* If request was incomplete, move the yet unmoved tail. */
+ if (tomove > 0)
+ return cbuffer_move(dest, src, tomove);
+
+ assert_invariants(dest);
+ assert_invariants(src);
+
+ return 0;
+}
+
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd)
+{
+ ssize_t r;
+
+ assert_invariants(cbuf);
+
+ /* Generate EAGAIN if needed (on no free space). */
+ if (cbuf->cb_free == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ /*
+ * If scatter-gather I/O is available, entiere buffer may be read at
+ * once. Otherwise only single segment can be read at time.
+ */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+ struct iovec areas[2];
+ int touse;
+
+ /* One or two segments? */
+ if (cbuf->cb_put >= cbuf->cb_get) {
+ /* Two. */
+ areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+ areas[0].iov_len = cbuf->cb_size - cbuf->cb_put;
+ areas[1].iov_base = cbuf->cb_base;
+ areas[1].iov_len = cbuf->cb_get;
+ touse = 2;
+ } else {
+ /* One. */
+ areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+ areas[0].iov_len = cbuf->cb_get - cbuf->cb_put;
+ touse = 1;
+ }
+ r = readv(fd, areas, touse);
+#else
+ /* Read into current segment. */
+ if (cbuf->cb_put >= cbuf->cb_get)
+ r = read(fd, cbuf->cb_base + cbuf->cb_put,
+ cbuf->cb_size - cbuf->cb_put);
+ else
+ r = read(fd, cbuf->cb_base + cbuf->cb_put,
+ cbuf->cb_get - cbuf->cb_put);
+#endif
+ /* Ack any successfully read data as written. */
+ if (r > 0)
+ ack_write(cbuf, (size_t)r);
+
+ assert_invariants(cbuf);
+
+ return r;
+}
+
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd)
+{
+ ssize_t r;
+
+ assert_invariants(cbuf);
+
+ /* Generate EAGAIN if needed (on no used space). */
+ if (cbuf->cb_used == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ /*
+ * If scatter-gather I/O is available, entiere buffer may be written
+ * at once. Otherwise only single segment can be written at time.
+ */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+ struct iovec areas[2];
+ int touse;
+
+ /* One or two segments? */
+ if (cbuf->cb_get >= cbuf->cb_put) {
+ /* Two. */
+ areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+ areas[0].iov_len = cbuf->cb_size - cbuf->cb_get;
+ areas[1].iov_base = cbuf->cb_base;
+ areas[1].iov_len = cbuf->cb_put;
+ touse = 2;
+ } else {
+ /* One. */
+ areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+ areas[0].iov_len = cbuf->cb_put - cbuf->cb_get;
+ touse = 1;
+ }
+ r = writev(fd, areas, touse);
+#else
+ /* Write current segment. */
+ if (cbuf->cb_get >= cbuf->cb_put)
+ r = write(fd, cbuf->cb_base + cbuf->cb_get,
+ cbuf->cb_size - cbuf->cb_get);
+ else
+ r = write(fd, cbuf->cb_base + cbuf->cb_get,
+ cbuf->cb_put - cbuf->cb_get);
+#endif
+ /* Ack any successfully written data as read. */
+ if (r > 0)
+ ack_read(cbuf, (size_t)r);
+
+ assert_invariants(cbuf);
+
+ return r;
+}
+
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+ size_t *length)
+{
+ assert_invariants(cbuf);
+
+ /* Compute segment base. */
+ *base = cbuf->cb_base + cbuf->cb_get;
+
+ if (!cbuf->cb_used) {
+ /* No used space -> empty segment. */
+ *length = 0;
+ } else if (cbuf->cb_get >= cbuf->cb_put) {
+ /* High segment is current. */
+ *length = cbuf->cb_size - cbuf->cb_get;
+ } else {
+ /* Low segment is current. */
+ *length = cbuf->cb_put - cbuf->cb_get;
+ }
+
+ assert_invariants(cbuf);
+}
+
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length)
+{
+ assert_invariants(cbuf);
+ /*
+ * This doesn't handle read being longer than single segment right,
+ * but that's undefined anyway.
+ */
+ ack_read(cbuf, length);
+ assert_invariants(cbuf);
+}
+
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+ size_t *length)
+{
+ assert_invariants(cbuf);
+
+ /* Compute segment base. */
+ *base = cbuf->cb_base + cbuf->cb_put;
+
+ if (!cbuf->cb_free) {
+ /* No free space -> empty segment. */
+ *length = 0;
+ } else if (cbuf->cb_put >= cbuf->cb_get) {
+ /* High segment is current. */
+ *length = cbuf->cb_size - cbuf->cb_put;
+ } else {
+ /* Low segment is current. */
+ *length = cbuf->cb_get - cbuf->cb_put;
+ }
+
+ assert_invariants(cbuf);
+}
+
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length)
+{
+ assert_invariants(cbuf);
+ /*
+ * This doesn't handle write being longer than single segment right,
+ * but that's undefined anyway.
+ */
+ ack_write(cbuf, length);
+ assert_invariants(cbuf);
+}
+
+
+void cbuffer_clear(struct cbuffer *cbuf)
+{
+ /*
+ * Just resetting pointers and values to initial defaults clears
+ * all data.
+ */
+ cbuf->cb_used = cbuf->cb_put = cbuf->cb_get = 0;
+ cbuf->cb_free = cbuf->cb_size;
+ assert_invariants(cbuf);
+}
+
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+ size_t limit)
+{
+ /* Limit the request to maximum possible and do read request. */
+ if (limit > cbuffer_used(cbuf))
+ limit = cbuffer_used(cbuf);
+ cbuffer_read(cbuf, dest, limit);
+ return limit;
+}
+
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+ size_t limit)
+{
+ /* Limit the request to maximum possible and do write request. */
+ if (limit > cbuffer_free(cbuf))
+ limit = cbuffer_free(cbuf);
+ cbuffer_write(cbuf, src, limit);
+ return limit;
+}
+
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+ size_t limit)
+{
+ /* Limit the request to maximum possible and do move request. */
+ if (limit > cbuffer_free(dest))
+ limit = cbuffer_free(dest);
+ if (limit > cbuffer_used(src))
+ limit = cbuffer_used(src);
+ cbuffer_move(dest, src, limit);
+ return limit;
+}
+
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src)
+{
+ size_t limit;
+ /*
+ * Limit the request to maximum possible and do move request.
+ * The move lacks limit so use free space in destination as
+ * first limit.
+ */
+ limit = cbuffer_free(dest);
+ if (limit > cbuffer_used(src))
+ limit = cbuffer_used(src);
+ cbuffer_move(dest, src, limit);
+ return limit;
+}
diff --git a/git-over-tls/cbuffer.h b/git-over-tls/cbuffer.h
new file mode 100644
index 0000000..4e07c5b
--- /dev/null
+++ b/git-over-tls/cbuffer.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _cbuffer__h__included__
+#define _cbuffer__h__included__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminology:
+ * Segment:
+ * Memory-contigious range extending from get pointer, put
+ * pointer or start of circular buffer till used/free type
+ * changes.
+ * Current segment:
+ * Segment starting from get pointer or put pointer.
+ *
+ */
+
+/* Primary circular buffer structure. Opaque type. */
+struct cbuffer;
+
+/*
+ * Create new circular buffer using specified data area. The data area
+ * is not copied and must remain until circular buffer is destroyed.
+ *
+ * Inputs:
+ * data The data area to back the circular buffer.
+ * datasize Size of backing data area.
+ *
+ * Outputs:
+ * return value The newly created circular buffer, or NULL
+ * if out of memory.
+ */
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize);
+
+
+/*
+ * Free circular buffer. Data area is not freed.
+ *
+ * Inputs:
+ * cbuf The circular buffer to free.
+ */
+void cbuffer_destroy(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes used in buffer (how many bytes can be read without
+ * writes).
+ *
+ * Inputs:
+ * cbuf The circular buffer to interrogate.
+ *
+ * Outputs:
+ * return value Number of bytes data in buffer.
+ */
+size_t cbuffer_used(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes free in buffer (how many bytes can be written
+ * without reads).
+ *
+ * Inputs:
+ * cbuf The circular buffer to interrogate.
+ *
+ * Outputs:
+ * return value Number of bytes free in buffer.
+ */
+size_t cbuffer_free(struct cbuffer *cbuf);
+
+/*
+ * Peek specified number of bytes from buffer. The bytes are not removed.
+ *
+ * Inputs:
+ * cbuf The circular buffer to peek.
+ * dest Destination buffer to store the peeked data to.
+ * toread Number of bytes to peek.
+ *
+ * Outputs:
+ * Return value 0 on success, -1 if buffer has insufficient
+ * amount of data.
+ *
+ */
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Read specified number of bytes from buffer. The bytes read are
+ * removed.
+ *
+ * Inputs:
+ * cbuf The circular buffer to read.
+ * dest Destination buffer to store the read data to.
+ * toread Number of bytes to read.
+ *
+ * Outputs:
+ * Return value 0 on success, -1 if buffer has insufficient
+ * amount of data.
+ */
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Write specified number of bytes to buffer.
+ *
+ * Inputs:
+ * cbuf The circular buffer to write.
+ * src Buffer to read the written data from.
+ * towrite Number of bytes to write.
+ *
+ * Outputs:
+ * Return value 0 on success, -1 if buffer has insufficient
+ * free space.
+ */
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+ size_t towrite);
+
+/*
+ * Move specified number of bytes from buffer to another. The data is
+ * removed from source buffer.
+ *
+ * Inputs:
+ * dest Destination circular buffer.
+ * src Source cirrcular buffer.
+ * tomove Number of bytes to move.
+ *
+ * Outputs:
+ * Return value 0 on success, -1 if insufficient source buffer
+ * data or insufficient destination buffer space.
+ */
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove);
+
+/*
+ * Call read on file descriptor. The call tries to read as much as
+ * possible in one go (up to entiere free space of destination
+ * buffer).
+ *
+ * Inputs:
+ * cbuf Circular buffer to store the data to.
+ * fd File descriptor to read.
+ *
+ * Outputs:
+ * Return value Number of bytes read (>0) on success, 0 if
+ * EOF on file descriptor or -1 if read failed.
+ * errno Set by read() or readv() on failure.
+ * EAGAIN if there is no free space in circular buffer.
+ */
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Call write on file descriptor. The call tries to write as much as
+ * possible in one go (up to entiere used space of soruce
+ * buffer).
+ *
+ * Inputs:
+ * cbuf Circular buffer to read data from.
+ * fd File descriptor to write.
+ *
+ * Outputs:
+ * Return value Number of bytes written (>=0) on success,
+ * -1 if write failed.
+ * errno Set by write() or writev() on failure.
+ * EAGAIN if there is no used space in circular buffer.
+ */
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Fill buffer read segment for direct from memory read operation on
+ * circular buffer. Any read operation on buffer invalidates segment.
+ *
+ * Inputs:
+ * cbuf Circular buffer to read from.
+ *
+ * Outputs:
+ * base Start address of segemnt is written here.
+ * length Length of segment is written here.
+ *
+ * Notes:
+ * - Returned length of 0 is only possible if there is no used
+ * space in buffer.
+ * - The entiere used space may not be returned in single segment.
+ */
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+ size_t *length);
+
+/*
+ * Commit read segment after direct read operation, marking data as
+ * read. The read bytes are removed.
+ *
+ * Inputs:
+ * cbuf Circular buffer read from.
+ * length Length of segment to mark as read. Must
+ * be at most the length gotten from
+ * cbuffer_fill_r_segment.
+ */
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Fill buffer write segment for direct to memory write operation on
+ * circular buffer. Any write operation on buffer invalidates segment.
+ *
+ * Inputs:
+ * cbuf Circular buffer to write to.
+ *
+ * Outputs:
+ * base Start address of segment is written here.
+ * length Length of segment is written here.
+ *
+ * Notes:
+ * - Returned length of 0 is only possible if there is no free
+ * space in buffer.
+ * - The entiere free space may not be returned in single segment.
+ */
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+ size_t *length);
+
+/*
+ * Commit write segment after direct write operation, marking data as
+ * written
+ *
+ * Inputs:
+ * cbuf Circular buffer written to.
+ * length Length of segment to mark as written. Must
+ * be at most the length gotten from
+ * cbuffer_fill_w_segment.
+ */
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Clear all data in cbuffer, freeing any used space.
+ *
+ * Inputs:
+ * cbuf Cirecular buffer to clear.
+ */
+void cbuffer_clear(struct cbuffer *cbuf);
+
+/*
+ * Read number of bytes from circular buffer smaller than limit.
+ * The read bytes are removed.
+ *
+ * Inputs:
+ * cbuf Circular buffer to read.
+ * dest Destination to store the read data.
+ * limit Maximum number of bytes to read.
+ *
+ * Outputs:
+ * Return value Number of bytes read.
+ */
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+ size_t limit);
+
+/*
+ * Write number of bytes to circular buffer smaller than limit.
+ *
+ * Inputs:
+ * cbuf Circular buffer to write.
+ * dest Source to read the wrritten data.
+ * limit Maximum number of bytes to write.
+ *
+ * Outputs:
+ * Return value Number of bytes written.
+ */
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+ size_t limit);
+
+/*
+ * Move number of bytes from circular buffer to another smaller than limit.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ * dest Destination circular buffer.
+ * src Source circular buffer.
+ * limit Maximum number of bytes to move.
+ *
+ * Outputs:
+ * Return value Number of bytes moved.
+ */
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+ size_t limit);
+
+/*
+ * Move as much bytes from circular buffer to another as possible.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ * dest Destination circular buffer.
+ * src Source circular buffer.
+ *
+ * Outputs:
+ * Return value Number of bytes moved.
+ */
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/git-over-tls/certificate.c b/git-over-tls/certificate.c
new file mode 100644
index 0000000..adeb776
--- /dev/null
+++ b/git-over-tls/certificate.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "cbuffer.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#include "run-command.h"
+#endif
+
+#define CERT_MAX 65536
+
+static long read_short(struct cbuffer *buffer)
+{
+ unsigned char x[2];
+ if (cbuffer_read(buffer, x, 2) < 0)
+ return -1;
+
+ return ((long)x[0] << 8) | ((long)x[1]);
+}
+
+
+static int unseal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,
+ const char *unsealer)
+{
+ struct child_process child;
+ char **argv;
+ char *unsealer_copy;
+ int splits = 0;
+ int escape = 0;
+ int ridx, widx, tidx;
+ const char *i;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ for (i = unsealer; *i; i++) {
+ if (escape)
+ escape = 0;
+ else if (*i == '\\')
+ escape = 1;
+ else if (*i == ' ')
+ splits++;
+ }
+
+ argv = xmalloc((splits + 2) * sizeof(char*));
+ argv[splits + 1] = NULL;
+
+ unsealer_copy = xstrdup(unsealer);
+ argv[0] = unsealer_copy;
+
+ ridx = 0;
+ widx = 0;
+ tidx = 1;
+ escape = 0;
+ while (unsealer_copy[ridx]) {
+ if (escape) {
+ escape = 0;
+ unsealer_copy[widx++] = unsealer_copy[ridx++];
+ } else if (unsealer_copy[ridx] == '\\') {
+ ridx++;
+ escape = 1;
+ } else if (unsealer_copy[ridx] == ' ') {
+ unsealer_copy[widx++] = '\0';
+ argv[tidx++] = unsealer_copy + widx;
+ ridx++;
+ } else
+ unsealer_copy[widx++] = unsealer_copy[ridx++];
+ }
+ unsealer_copy[widx] = '\0';
+
+ memset(&child, 0, sizeof(child));
+ child.argv = (const char**)argv;
+ child.in = -1;
+ child.out = -1;
+ child.err = 0;
+ if (start_command(&child))
+ die("Running keypair unsealer command failed");
+
+ while (1) {
+ int bound;
+ fd_set rf;
+ fd_set wf;
+ int r;
+
+ FD_ZERO(&rf);
+ FD_ZERO(&wf);
+ FD_SET(child.out, &rf);
+ if (cbuffer_used(sealed))
+ FD_SET(child.in, &wf);
+ else
+ close(child.in);
+
+ if (cbuffer_used(sealed))
+ bound = ((child.out > child.in) ? child.out :
+ child.in) + 1;
+ else
+ bound = child.out + 1;
+
+ r = select(bound, &rf, &wf, NULL, NULL);
+ if (r < 0 && r != EINTR)
+ die_errno("Select failed");
+ if (r < 0) {
+ FD_ZERO(&rf);
+ FD_ZERO(&wf);
+ perror("select");
+ }
+
+ if (FD_ISSET(child.out, &rf)) {
+ r = cbuffer_read_fd(unsealed, child.out);
+ if (r < 0 && errno != EINTR && errno != EAGAIN)
+ die_errno("Read from unsealer failed");
+ if (r < 0 && errno == EAGAIN)
+ if (!cbuffer_free(unsealed))
+ die("Keypair too big");
+ if (r < 0)
+ perror("read");
+ if (r == 0)
+ break;
+ }
+
+ if (FD_ISSET(child.in, &wf)) {
+ r = cbuffer_write_fd(sealed, child.in);
+ if (r < 0 && errno == EPIPE)
+ die("Unsealer exited unexpectedly");
+ if (r < 0 && errno != EINTR && errno != EAGAIN)
+ die_errno("Write to unsealer failed");
+ if (r < 0)
+ perror("write");
+ }
+ }
+
+ if (finish_command(&child))
+ die("Keypair unsealer command failed");
+
+ return 0;
+}
+
+
+struct certificate parse_certificate(const char *name, int *errorcode)
+{
+ struct cbuffer *sealed = NULL;
+ struct cbuffer *unsealed = NULL;
+ unsigned char sealed_buf[CERT_MAX];
+ unsigned char unsealed_buf[CERT_MAX];
+ struct certificate cert;
+ int fd;
+ unsigned char head[10];
+ long tmp;
+
+ *errorcode = CERTERR_OK;
+ cert.public_key.data = NULL;
+ cert.public_key.size = 0;
+ cert.private_key.data = NULL;
+ cert.private_key.size = 0;
+
+ sealed = cbuffer_create(sealed_buf, CERT_MAX);
+ if (!sealed)
+ die("Ran out of memory");
+
+ unsealed = cbuffer_create(unsealed_buf, CERT_MAX);
+ if (!unsealed)
+ die("Ran out of memory");
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ *errorcode = CERTERR_NOCERT;
+ else
+ *errorcode = CERTERR_CANTREAD;
+ goto out_unsealed;
+ }
+
+ while (1) {
+ ssize_t r = cbuffer_read_fd(sealed, fd);
+ if (r == 0)
+ break;
+ if (r < 0 && errno == EAGAIN) {
+ if (!cbuffer_free(sealed)) {
+ *errorcode = CERTERR_TOOBIG;
+ goto out_close;
+ }
+ } else if (r < 0 && errno != EINTR) {
+ *errorcode = CERTERR_CANTREAD;
+ goto out_close;
+ }
+ }
+
+ head[9] = 0;
+ if (cbuffer_read(sealed, head, 9) < 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+ if (strcmp((char*)head, "GITSSCERT") &&
+ strcmp((char*)head, "GITSUCERT")) {
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+
+ if (!strcmp((char*)head, "GITSSCERT")) {
+ /* Sealed certificate. */
+ char *unsealer;
+ int s;
+ tmp = read_short(sealed);
+ if (tmp <= 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+ unsealer = xmalloc(tmp + 1);
+ unsealer[tmp] = '\0';
+ if (cbuffer_read(sealed, (unsigned char*)unsealer, tmp) < 0) {
+ free(unsealer);
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+ s = unseal_cert(sealed, unsealed, unsealer);
+ free(unsealer);
+ if (s < 0) {
+ *errorcode = s;
+ goto out_close;
+ }
+ } else {
+ /* Unsealed certificate. */
+ cbuffer_move_nolimit(unsealed, sealed);
+ }
+
+ cert.private_key.data = NULL;
+ cert.public_key.data = NULL;
+
+ tmp = read_short(unsealed);
+ if (tmp < 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+ cert.private_key.size = tmp;
+ cert.private_key.data = (unsigned char*)gnutls_malloc(tmp);
+ if (!cert.private_key.data)
+ die("Ran out of memory");
+
+ if (cbuffer_read(unsealed, cert.private_key.data,
+ cert.private_key.size) < 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_private;
+ }
+
+ tmp = read_short(unsealed);
+ if (tmp < 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_close;
+ }
+ cert.public_key.size = tmp;
+ cert.public_key.data = (unsigned char*)gnutls_malloc(tmp);
+ if (!cert.public_key.data)
+ die("Ran out of memory");
+
+ if (cbuffer_read(unsealed, cert.public_key.data,
+ cert.public_key.size) < 0) {
+ *errorcode = CERTERR_INVALID;
+ goto out_public;
+ }
+
+ if (cbuffer_used(unsealed)) {
+ *errorcode = CERTERR_INVALID;
+ goto out_public;
+ }
+
+ goto out_close;
+
+out_public:
+ gnutls_free(cert.private_key.data);
+out_private:
+ gnutls_free(cert.private_key.data);
+out_close:
+ close(fd);
+out_unsealed:
+ cbuffer_destroy(unsealed);
+ cbuffer_destroy(sealed);
+ return cert;
+}
+
+const char *cert_parse_strerr(int errcode)
+{
+ switch(errcode) {
+ case CERTERR_OK:
+ return "Success";
+ case CERTERR_NOCERT:
+ return "No such keypair";
+ case CERTERR_INVALID:
+ return "Keypair file corrupt";
+ case CERTERR_CANTREAD:
+ return "Can't read keypair file";
+ case CERTERR_TOOBIG:
+ return "Keypair too big";
+ }
+ return "<Unknown error>";
+}
diff --git a/git-over-tls/certificate.h b/git-over-tls/certificate.h
new file mode 100644
index 0000000..5ee355a
--- /dev/null
+++ b/git-over-tls/certificate.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _certificate__h__included__
+#define _certificate__h__included__
+
+#include <gnutls/gnutls.h>
+
+#define CERTERR_OK 0
+#define CERTERR_NOCERT -2
+#define CERTERR_INVALID -3
+#define CERTERR_CANTREAD -4
+#define CERTERR_TOOBIG -5
+
+struct certificate
+{
+ gnutls_datum_t public_key;
+ gnutls_datum_t private_key;
+};
+
+struct certificate parse_certificate(const char *name, int *errorcode);
+const char *cert_parse_strerr(int errcode);
+
+#endif
diff --git a/git-over-tls/connect.c b/git-over-tls/connect.c
new file mode 100644
index 0000000..8f19bc8
--- /dev/null
+++ b/git-over-tls/connect.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "connect.h"
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifndef WIN32
+#include <sys/un.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+static int connect_unix(const char* path)
+{
+#ifndef WIN32
+ struct sockaddr_un saddru;
+ int fd, ret, plen;
+
+ if (strlen(path) > UNIX_PATH_MAX - 1)
+ die("Unix socket path too long");
+
+ saddru.sun_family = AF_UNIX;
+ strcpy(saddru.sun_path, path);
+ if (*saddru.sun_path == '@')
+ *saddru.sun_path = '\0';
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ die_errno("Can't create socket");
+ if (*path == '@')
+ plen = (int)((char*)saddru.sun_path - (char*)&saddru) +
+ strlen(path);
+ else
+ plen = (int)sizeof(saddru);
+ ret = connect(fd, (struct sockaddr*)&saddru, plen);
+ if (ret < 0) {
+ die_errno("Can't connect to %s", path);
+ }
+ return fd;
+#else
+ die("Unix domain sockets not supported by this build");
+#endif
+}
+
+static int connect_address(const char* host, unsigned short port,
+ int protocol, struct sockaddr* addr, int size)
+{
+ char address[1024];
+ int fd, ret;
+ const unsigned char* _addr;
+
+ fd = socket(addr->sa_family, SOCK_STREAM, protocol);
+ if (fd < 0) {
+ error("Can't create socket: %s", strerror(errno));
+ return -1;
+ }
+ switch(addr->sa_family) {
+ case AF_INET:
+ _addr = (const unsigned char*)
+ &(((struct sockaddr_in*)addr)->sin_addr.s_addr);
+ sprintf(address, "%u.%u.%u.%u", _addr[0], _addr[1],
+ _addr[2], _addr[3]);
+ break;
+#ifndef NO_IPV6
+ case AF_INET6:
+ _addr = (const unsigned char*)
+ ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr;
+ sprintf(address, "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+ "%02X%02X:%02X%02X:%02X%02X:%02X%02X",
+ _addr[0], _addr[1], _addr[2], _addr[3],
+ _addr[4], _addr[5], _addr[6], _addr[7],
+ _addr[8], _addr[9], _addr[10], _addr[11],
+ _addr[12], _addr[13], _addr[14], _addr[15]);
+ break;
+#endif
+ default:
+ sprintf(address, "<unknown address type>");
+ break;
+ }
+ ret = connect(fd, addr, size);
+ if (ret < 0) {
+ error("Can't connect to host %s[%s] port %u: %s",
+ host, address, port, strerror(errno));
+ return -1;
+ }
+ return fd;
+}
+
+int connect_gethostbyname(const char* _host,
+ unsigned short _port, uint32_t scope)
+{
+#ifndef NO_IPV6
+ struct addrinfo hints;
+ struct addrinfo* returned;
+ char port[10];
+ int fd, ret;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+
+ sprintf(port, "%u", _port);
+ ret = getaddrinfo(_host, port, &hints, &returned);
+ if (ret)
+ die("getaddrinfo(%s, %s, ...): %s", _host, port,
+ gai_strerror(ret));
+
+ while (returned) {
+ if (returned->ai_family == AF_INET6)
+ ((struct sockaddr_in6*)returned->ai_addr)->
+ sin6_scope_id = scope;
+ else if (scope)
+ warning("Scope is ignored for non-IPv6 addresses");
+
+ fd = connect_address(_host, _port, returned->ai_protocol,
+ returned->ai_addr, returned->ai_addrlen);
+
+ if (fd >= 0)
+ goto out;
+ returned = returned->ai_next;
+ }
+
+ die("Can't connect to host %s", _host);
+
+out:
+ freeaddrinfo(returned);
+ return fd;
+#else
+ struct hostent *host;
+ int fd;
+ static struct sockaddr_in saddr4;
+
+ host = gethostbyname(_host);
+ if (!host || !host->h_addr)
+ die("Can't find host %s", _host);
+
+next_address:
+ if (host->h_addrtype == AF_INET) {
+ memset(&saddr4, 0, sizeof(saddr4));
+ saddr4.sin_family = AF_INET;
+ memcpy(&saddr4.sin_addr, host->h_addr_list[0], 4);
+ saddr4.sin_port = htons(_port);
+ fd = connect_address(_host, _port, 0,
+ (struct sockaddr*)&saddr4,
+ sizeof(struct sockaddr_in));
+ if (scope)
+ warning("Scope is ignored for non-IPv6 addresses");
+ } else
+ die("Host %s has unknown address type", _host);
+
+ if (fd >= 0)
+ goto out;
+
+ host->h_addr_list++;
+ if (host->h_addr_list)
+ goto next_address;
+
+ if (scope > 0)
+ die("Can't connect to host %s%%u", _host, scope);
+ else
+ die("Can't connect to host %s", _host);
+out:
+ return fd;
+#endif
+}
+
+/* Parse character as base-10 digit. */
+static int char_to_int(char ch)
+{
+ switch(ch) {
+ case '0':
+ return 0;
+ case '1':
+ return 1;
+ case '2':
+ return 2;
+ case '3':
+ return 3;
+ case '4':
+ return 4;
+ case '5':
+ return 5;
+ case '6':
+ return 6;
+ case '7':
+ return 7;
+ case '8':
+ return 8;
+ case '9':
+ return 9;
+ default:
+ return -1;
+ }
+}
+
+static uint32_t touint32_t(const char* num)
+{
+ uint32_t x = 0;
+ unsigned i;
+
+ /* Blank string is not valid number. */
+ if (!*num)
+ die("Invalid scope id '%s'", num);
+
+ /* 0 is special case, makes it easier to deal with zeros. */
+ if (!strcmp(num, "0"))
+ return 0;
+
+ /*
+ * Valid uints numbers are 0-2^32-1, but because we handled 0 as
+ * special case, the valid range can be 1-2^32-1 here.
+ */
+ for (i = 0; num[i]; i++) {
+ char ch = char_to_int(num[i]);
+ if (ch < 0)
+ die("Invalid scope id '%s'", num);
+ if (ch == 0 && x == 0)
+ die("Invalid scope id '%s'", num);
+ if (x > 429496729 || (x > 429496728 && ch > 5))
+ die("Invalid scope id '%s'", num);
+ x = x * 10 + ch;
+ }
+ return x;
+}
+
+int connect_host(const char* _host, unsigned short _port)
+{
+ uint32_t scope = 0;
+ char* scopestart;
+ char* hostcopy;
+
+ if (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/'))
+ return connect_unix(_host);
+
+ hostcopy = xmalloc(strlen(_host) + 1);
+ strcpy(hostcopy, _host);
+ scopestart = strchr(hostcopy, '%');
+ if (scopestart) {
+ *(scopestart++) = '\0';
+ scope = touint32_t(scopestart);
+ }
+
+ return connect_gethostbyname(hostcopy, _port, scope);
+}
diff --git a/git-over-tls/connect.h b/git-over-tls/connect.h
new file mode 100644
index 0000000..90e36cd
--- /dev/null
+++ b/git-over-tls/connect.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _connect__h__included__
+#define _connect__h__included__
+
+//Returns connected fd or dies.
+int connect_host(const char* host, unsigned short port);
+
+#endif
diff --git a/git-over-tls/genkeypair.c b/git-over-tls/genkeypair.c
new file mode 100644
index 0000000..4f2142c
--- /dev/null
+++ b/git-over-tls/genkeypair.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void write_cert(int server_mode);
+
+static void do_help()
+{
+ printf("gits-generate-keypair: User keypair generator.\n");
+ printf("Command line options:\n");
+ printf("--help\n");
+ printf("\tThis help\n");
+ printf("\n");
+ printf("Note: Keep generated keypair files private!\n");
+ printf("Use gits-get-key-name to get public short\n");
+ printf("representation of keypair for authorization.\n");
+ printf("\n");
+ printf("WARNING: Don't let keypairs to be tampered with!\n");
+ printf("WARNING: Tampered keypairs may do very nasty things\n");
+ printf("WARNING: if used.\n");
+ exit(0);
+}
+
+
+int main(int argc, char **argv)
+{
+ if (argc > 1 && !strcmp(argv[1], "--help"))
+ do_help();
+ write_cert(0);
+ return 0;
+}
diff --git a/git-over-tls/gensrpverifier.c b/git-over-tls/gensrpverifier.c
new file mode 100644
index 0000000..751854a
--- /dev/null
+++ b/git-over-tls/gensrpverifier.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "prompt.h"
+#include <stdio.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static void do_help()
+{
+ printf("gits-generate-srp-verifier: Generate SRP verifiers for\n");
+ printf("password authentication\n");
+ printf("Command line:\n");
+ printf("--help\n");
+ printf("\tThis help\n");
+ printf("\n");
+ printf("Note: Server needs SRP verifier in order to do password\n");
+ printf("authentication. Usernames are assigned by repostiory\n");
+ printf("hosting admin, just using arbitrary names doesn't work.\n");
+ exit(0);
+}
+
+
+struct field
+{
+ const char *f_name;
+ const char *f_generator;
+ const char *f_prime;
+};
+
+struct field fields[] = {
+ {
+ "standard 1024 bit field", "2",
+ "Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE"
+ "3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Z"
+ "jl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ"
+ },
+ {
+ "standard 1536 bit field", "2",
+ "dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5"
+ "LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTa"
+ "u/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtm"
+ "cIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx"
+ },
+ {
+ "standard 2048 bit field", "2",
+ "2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJt"
+ "L8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL"
+ ".NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nq"
+ "ha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXq"
+ "vp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUD"
+ "o/DQEyW.d4H.UIlzp"
+ },
+ {NULL, NULL, NULL}
+};
+
+
+unsigned char xfact[16] = {
+0x9D, 0x0F, 0x49, 0xD4,
+0x73, 0x88, 0xC7, 0xFF,
+0xFD, 0x24, 0x6A, 0x0F,
+0x94, 0x78, 0x0C, 0x14
+};
+
+unsigned char rnd[16] = {
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+
+static void add(unsigned char *res, unsigned char *a, unsigned char *b)
+{
+ unsigned char carry = 0;
+ unsigned i;
+
+ for (i = 0; i < 16; i++) {
+ unsigned char newcarry = 0;
+
+ if ((unsigned char)(a[i] + b[i]) < a[i])
+ newcarry++;
+ res[i] = a[i] + b[i];
+ if (res[i] + carry < res[i])
+ newcarry++;
+ res[i] += carry;
+ carry = newcarry;
+ }
+ while (carry) {
+ carry = 159;
+
+ for (i = 0; i < 16; i++) {
+ int newcarry = 0;
+
+ if ((unsigned char)(res[i] + carry) < res[i])
+ newcarry++;
+ res[i] += carry;
+ carry = newcarry;
+ }
+ }
+ for (i = 1; i < 16; i++)
+ if (res[i] < 255)
+ goto skip;
+
+ if (res[0] > 0x60) {
+ for (i = 1; i < 16; i++)
+ res[i] = 0;
+ res[0] -= 0x61;
+ }
+skip:
+ ;
+}
+
+void update_xfact()
+{
+ unsigned char xfact2[16];
+ unsigned char xfact4[16];
+ unsigned char xfact5[16];
+ add(xfact2, xfact, xfact);
+ add(xfact4, xfact2, xfact2);
+ add(xfact5, xfact4, xfact);
+ memcpy(xfact, xfact5, 16);
+}
+
+void update_rnd(unsigned char ch)
+{
+ unsigned char rndt[16];
+ unsigned i;
+ for (i = 0; i < 8; i++) {
+ if ((ch >> i) % 2) {
+ add(rndt, rnd, xfact);
+ memcpy(rnd, rndt, 16);
+ }
+ update_xfact();
+ }
+}
+
+void update_rnd_str(const char *ch)
+{
+ while (ch && *ch)
+ update_rnd((unsigned char)*(ch++));
+}
+
+
+void decode_element(gnutls_datum_t *decode, const char *encoded)
+{
+ int s;
+ gnutls_datum_t _base64;
+ size_t base64len, reslen, reslen2;
+
+ base64len = strlen(encoded);
+ reslen2 = reslen = (3 * base64len + 1) / 4;
+
+ _base64.data = (unsigned char*)encoded;
+ _base64.size = base64len;
+
+ decode->size = reslen;
+ decode->data = xmalloc(reslen);
+ s = gnutls_srp_base64_decode(&_base64, (char*)decode->data, &reslen2);
+ if (s < 0)
+ die("Unable to decode base64 data");
+ else if (reslen != reslen2)
+ die("Base64 dlength calculation incorrect. Calculated %lu, "
+ "got %lu", (unsigned long)reslen, (unsigned long)reslen2);
+}
+
+unsigned char *encode_element(gnutls_datum_t *data)
+{
+ int s;
+ size_t reslen2;
+ unsigned char *res;
+
+ reslen2 = (4 * data->size + 2) / 3;
+
+ res = xmalloc(reslen2 + 1);
+ s = gnutls_srp_base64_encode(data, (char*)res, &reslen2);
+ if (s < 0)
+ die("Unable to encode base64 data");
+ res[reslen2] = '\0';
+ return res;
+}
+
+char *generate_srp_line(const char *username,
+ const char *password, const char *junk, struct field *field)
+{
+ gnutls_datum_t salt;
+ gnutls_datum_t g;
+ gnutls_datum_t n;
+ gnutls_datum_t res;
+ int s;
+ char *retline = NULL;
+ unsigned char *encoded_salt;
+ unsigned char *encoded_verifier;
+ update_rnd_str(username);
+ update_rnd_str(":::::");
+ update_rnd_str(junk);
+
+ salt.data = rnd;
+ salt.size = 16;
+ decode_element(&g, field->f_generator);
+ decode_element(&n, field->f_prime);
+
+ s = gnutls_srp_verifier(username, password, &salt, &g, &n, &res);
+ if (s < 0)
+ die("Unable to generate SRP verifier: %s",
+ gnutls_strerror(s));
+
+ encoded_verifier = encode_element(&res);
+ encoded_salt = encode_element(&salt);
+
+ retline = xmalloc(5 + strlen(username) +
+ strlen((char*)encoded_salt) +
+ strlen((char*)encoded_verifier) +
+ strlen(field->f_generator) +
+ strlen(field->f_prime));
+ retline[0] = '\0';
+ strcat(retline, username);
+ strcat(retline, ":");
+ strcat(retline, (char*)encoded_salt);
+ strcat(retline, ":");
+ strcat(retline, (char*)encoded_verifier);
+ strcat(retline, ":");
+ strcat(retline, field->f_generator);
+ strcat(retline, ":");
+ strcat(retline, field->f_prime);
+
+ free(encoded_verifier);
+ free(encoded_salt);
+ free(res.data);
+ free(g.data);
+ free(n.data);
+
+ return retline;
+}
+
+#define LINELEN 69
+
+static void flush_to_file(FILE *out, const char *srpline)
+{
+ char linebuffer[LINELEN + 2];
+
+ linebuffer[LINELEN] = '\\';
+ linebuffer[LINELEN + 1] = '\0';
+
+ while (*srpline) {
+ size_t r;
+ strncpy(linebuffer, srpline, LINELEN);
+ r = strlen(srpline);
+ if (r == LINELEN)
+ linebuffer[LINELEN] = '\0';
+ if (r <= LINELEN)
+ srpline += r;
+ else
+ srpline += LINELEN;
+ fprintf(out, "%s\n", linebuffer);
+ }
+}
+
+int fill_rnd()
+{
+ int fd;
+ int fill = 0;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ while (fill < 16) {
+ ssize_t r;
+ r = read(fd, rnd + fill, 16 - fill);
+ if (r < 0 && errno != EINTR && errno != EAGAIN) {
+ close(fd);
+ return 0;
+ } else if (r == 0) {
+ close(fd);
+ return 0;
+ } else {
+ fill += r;
+ }
+ }
+ close(fd);
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ int idx, midx = 0;
+ char *username = NULL;
+ char *password = NULL;
+ char *password2 = NULL;
+ char *junk = NULL;
+ char *field = NULL;
+ char *file = NULL;
+ char *ans = NULL;
+ char *end = NULL;
+ FILE *filp = stdout;
+
+ if (argc > 1 && !strcmp(argv[1], "--help"))
+ do_help();
+
+username_again:
+ free(username);
+ username = prompt_string("Enter username", 0);
+ if (!*username) {
+ fprintf(stderr, "Error: Bad username\n");
+ goto username_again;
+ }
+
+ if (fill_rnd())
+ goto no_junk_prompt;
+junk_again:
+ free(junk);
+ junk = prompt_string("Enter some garbage from keyboard (min 32 "
+ "chars)", 1);
+ if (strlen(junk) < 32) {
+ fprintf(stderr, "Error: Garbage needs to be at least "
+ "32 characters\n");
+ goto junk_again;
+ }
+no_junk_prompt:
+
+passwords_again:
+ free(password);
+ free(password2);
+ password = prompt_string("Enter password", 1);
+ password2 = prompt_string("Enter password again", 1);
+ if (strcmp(password, password2)) {
+ fprintf(stderr, "Error: Passwords don't match\n");
+ goto passwords_again;
+ }
+
+field_again:
+ free(field);
+ for (midx = 0; fields[midx].f_name; midx++) {
+ printf("%i) %s\n", midx + 1, fields[midx].f_name);
+ }
+ field = prompt_string("Pick field", 0);
+ idx = (int)strtoul(field, &end, 10) - 1;
+ if (idx < 0 || idx >= midx || !*field || *end) {
+ printf("%i %i %i %i\n", idx, midx, *field, *end);
+ fprintf(stderr, "Error: Invalid choice\n");
+ goto field_again;
+ }
+
+file_again:
+ file = prompt_string("Filename to save as (enter for dump to "
+ "terminal)", 0);
+ if (*file) {
+ filp = fopen(file, "w");
+ if (!filp) {
+ fprintf(stderr, "Can't open \"%s\"\n", file);
+ goto file_again;
+ }
+ }
+
+ ans = generate_srp_line(username, password, junk, fields + idx);
+ flush_to_file(filp, ans);
+ if (filp != stdout)
+ fclose(filp);
+ return 0;
+}
diff --git a/git-over-tls/getkeyid.c b/git-over-tls/getkeyid.c
new file mode 100644
index 0000000..091e60b
--- /dev/null
+++ b/git-over-tls/getkeyid.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "home.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void do_help()
+{
+ printf("gits-get-key-name: Get name for keypair or hostkey.\n");
+ printf("Command line options:\n");
+ printf("--help\n");
+ printf("\tThis help\n");
+ printf("<keyfile>\n");
+ printf("\tRead key file <keyfile>. Name must contain at least\n");
+ printf("\tone '/'\n");
+ printf("<keyname>\n");
+ printf("\tRead key named <keyname>. Name must not contain\n");
+ printf("\t'/'\n");
+ printf("\n");
+ printf("Note: These key names are used in hostkey database and\n");
+ printf("as user names seen by authorization program.\n");
+ exit(0);
+}
+
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+
+int main(int argc, char **argv)
+{
+ struct certificate certificate;
+ gnutls_openpgp_crt_t cert;
+ char filename[PATH_MAX + 1];
+ unsigned char key[KEYBUF];
+ int s;
+ unsigned vout;
+
+ if (argc != 2) {
+ fprintf(stderr, "syntax: %s <keyname>\n", argv[0]);
+ fprintf(stderr, "syntax: %s <keyfile>\n", argv[0]);
+ return 1;
+ }
+
+ if (!strcmp(argv[1], "--help"))
+ do_help();
+
+ if (strchr(argv[1], '/'))
+ s = snprintf(filename, PATH_MAX + 1, "%s", argv[1]);
+ else
+ s = snprintf(filename, PATH_MAX + 1, "%s/.gits/keys/%s",
+ get_home(), argv[1]);
+ if (s < 0 || s > PATH_MAX)
+ die("Insanely long homedir/keyname");
+
+ s = gnutls_global_init();
+ if (s < 0)
+ die("Can't initialize GnuTLS: %s",
+ gnutls_strerror(s));
+
+
+ certificate = parse_certificate(filename, &s);
+ if (s) {
+ if (s == CERTERR_NOCERT)
+ die("Can't find key %s", filename);
+ else if (s == CERTERR_CANTREAD)
+ die_errno("Can't read key");
+ else
+ die("Can't parse key: %s",
+ cert_parse_strerr(s));
+ }
+
+ s = gnutls_openpgp_crt_init(&cert);
+ if (s < 0)
+ die("Can't allocate space for key: %s",
+ gnutls_strerror(s));
+
+ s = gnutls_openpgp_crt_import(cert, &certificate.public_key,
+ GNUTLS_OPENPGP_FMT_RAW);
+ if (s < 0)
+ die("Bad key: %s", gnutls_strerror(s));
+
+ s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+ if (s < 0)
+ die("Bad key: %s", gnutls_strerror(s));
+ if (vout)
+ die("Bad key: Validation failed");
+
+ vout = KEYBUF;
+ s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+ if (s < 0)
+ die("Bad key: %s", gnutls_strerror(s));
+
+ gnutls_openpgp_crt_deinit(cert);
+
+ printf("openpgp-");
+ for (s = 0; s < (int)vout; s++)
+ printf("%02x", key[s]);
+ printf("\n");
+ return 0;
+}
diff --git a/git-over-tls/gits-send-special-command b/git-over-tls/gits-send-special-command
new file mode 100755
index 0000000..ade530d
--- /dev/null
+++ b/git-over-tls/gits-send-special-command
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (C) Ilari Liusvaara 2009
+#
+# This code is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+
+if test "x${1}" == "x--help"
+then
+ echo "gits-send-special-command: Send special command to "
+ echo "server"
+ echo "command line:"
+ echo "--help"
+ echo -e "\x09This help"
+ echo "<service> <URL>"
+ echo -e "\x09Send request for <service> to specified <URL>."
+ exit 0
+fi
+
+git-remote-gits --service=$1 $2
diff --git a/git-over-tls/home.c b/git-over-tls/home.c
new file mode 100644
index 0000000..b41dbfa
--- /dev/null
+++ b/git-over-tls/home.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+const char *get_home()
+{
+ static char *home = NULL;
+ const char *tmpans;
+#ifndef WIN32
+ struct passwd *user;
+#endif
+
+ if (home)
+ return home;
+
+ if (getenv("HOME")) {
+ tmpans = getenv("HOME");
+ goto got_it;
+ }
+
+#ifndef WIN32
+ user = getpwuid(getuid());
+ if (user && user->pw_dir) {
+ tmpans = user->pw_dir;
+ goto got_it;
+ }
+#endif
+
+ die("Can't obtain home directory of current user");
+got_it:
+ home = xstrdup(tmpans);
+ return home;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/home.h
new file mode 100644
index 0000000..133ee78
--- /dev/null
+++ b/git-over-tls/home.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _home__h__included__
+#define _home__h__included__
+
+const char *get_home();
+
+#endif
diff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c
new file mode 100644
index 0000000..28df0e5
--- /dev/null
+++ b/git-over-tls/hostkey.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "hostkey.h"
+#include "home.h"
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+#define MAXLINE 2048
+
+void check_hostkey(gnutls_session_t session, const char *hostname)
+{
+ const gnutls_datum_t *certificate = NULL;
+ unsigned int cert_size = 0;
+ gnutls_openpgp_crt_t cert;
+ int s;
+ unsigned int vout;
+ unsigned char key[KEYBUF];
+ FILE *hostfile;
+ char hostfilepath[PATH_MAX + 1];
+ char linebuffer[MAXLINE];
+
+ certificate = gnutls_certificate_get_peers(session, &cert_size);
+ if (!certificate)
+ die("Server didn't send a hostkey");
+
+ s = gnutls_openpgp_crt_init(&cert);
+ if (s < 0)
+ die("Can't allocate space for hostkey: %s",
+ gnutls_strerror(s));
+
+ s = gnutls_openpgp_crt_import(cert, certificate,
+ GNUTLS_OPENPGP_FMT_RAW);
+ if (s < 0)
+ die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+ /* Defend against subkey attack. */
+ s = gnutls_openpgp_crt_get_subkey_count(cert);
+ if (s != 0)
+ die("Server sent bad hostkey: Subkeys are not allowed");
+
+ s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+ if (s < 0)
+ die("Server sent bad hostkey: %s", gnutls_strerror(s));
+ if (vout)
+ die("Server sent bad hostkey: Validation failed");
+
+ vout = KEYBUF;
+ s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+ if (s < 0)
+ die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+ gnutls_openpgp_crt_deinit(cert);
+
+ s = snprintf(hostfilepath, PATH_MAX + 1, "%s/.gits/hostkeys",
+ get_home());
+ if (s < 0 || s > PATH_MAX)
+ die("Home directory path insanely long");
+
+ hostfile = fopen(hostfilepath, "r");
+ if (!hostfile)
+ die_errno("Can't open .gits/hostkeys");
+
+ while (fgets(linebuffer, MAXLINE - 2, hostfile)) {
+ char *split;
+ if (!*linebuffer || *linebuffer == '#')
+ continue;
+ if (linebuffer[strlen(linebuffer) - 1] == '\n')
+ linebuffer[strlen(linebuffer) - 1] = '\0';
+
+ split = strchr(linebuffer, ' ');
+ if (!split)
+ continue;
+ *split = '\0';
+ if (strcmp(linebuffer, hostname))
+ continue;
+
+ /*
+ * Be nice to users and strip this in case it gets
+ * retained from key id calculator.
+ */
+ if (!strncmp(split + 1, "openpgp-", 8))
+ split += 8;
+
+ for (s = 0; s < vout; s++) {
+ char buffer[3];
+ sprintf(buffer, "%02x", (int)key[s]);
+ if (buffer[0] != split[2 * s + 1])
+ die("HOST KEY MISMATCH FOR HOST %s!",
+ hostname);
+ if (buffer[1] != split[2 * s + 2])
+ die("HOST KEY MISMATCH FOR HOST %s!",
+ hostname);
+ }
+ if (split[2 * vout + 1])
+ die("HOST KEY MISMATCH FOR HOST %s!",
+ hostname);
+ goto ok;
+ }
+ die("Hostkey for %s not found in hostkeys list", hostname);
+ok:
+ fclose(hostfile);
+}
diff --git a/git-over-tls/hostkey.h b/git-over-tls/hostkey.h
new file mode 100644
index 0000000..c0e7dfe
--- /dev/null
+++ b/git-over-tls/hostkey.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _hostkey__h__included__
+#define _hostkey__h__included__
+
+#include <gnutls/gnutls.h>
+
+void check_hostkey(gnutls_session_t session, const char *hostname);
+
+#endif
diff --git a/git-over-tls/hostkeymanager.c b/git-over-tls/hostkeymanager.c
new file mode 100644
index 0000000..2df09e0
--- /dev/null
+++ b/git-over-tls/hostkeymanager.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#define BUFSIZE 8192
+
+static void do_help()
+{
+ printf("gits-hostkey: Add, update or delete entries in hostkey.\n");
+ printf("database.\n");
+ printf("Command line:\n");
+ printf("--help\n");
+ printf("\tThis help\n");
+ printf("list\n");
+ printf("\tList hosts known and their keys\n");
+ printf("add <host> <key>\n");
+ printf("\tAdd <host> to database with key <key>\n");
+ printf("update <host> <key>\n");
+ printf("\tUpdate <host> to database to use key <key>\n");
+ printf("delete <host>\n");
+ printf("\tDelete key for <host>\n");
+ exit(0);
+}
+
+
+struct hostkey
+{
+ /* Hostname. NULL if not valid line. */
+ char *h_hostname;
+ /* Hostkey. NULL if not valid line. */
+ char *h_hostkey;
+ /* Line. Non-NULL if hostname/hoskey is NULL. */
+ char *h_line;
+ /* Next line. */
+ struct hostkey *h_next;
+};
+
+struct hostkey *first_hostkey = NULL;
+struct hostkey *last_hostkey = NULL;
+
+int ensure_leading_directories()
+{
+ struct stat s;
+ char fsobj[BUFSIZE];
+ int r;
+
+ sprintf(fsobj, "%s/.gits", get_home());
+ r = stat(fsobj, &s);
+ if (r < 0 && errno != ENOENT)
+ die_errno("Stat $HOME/.gits");
+ else if (r == 0 && !S_ISDIR(s.st_mode))
+ die("$HOME/.gits exists but is not a directory");
+ else if (r < 0) {
+ /* Need to create it. */
+ if (mkdir(fsobj, 0700) < 0)
+ die_errno("Create $HOME/.gits failed");
+ }
+ /* Otherwise, r == 0 && S_ISDIR(s), which is OK. */
+ sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+ r = stat(fsobj, &s);
+ if (r < 0 && errno != ENOENT)
+ die_errno("Stat $HOME/.gits/hostkeys");
+ else if (r == 0 && !S_ISREG(s.st_mode))
+ die("$HOME/.gits/hostkeys exists but is not a file");
+ /* Doesn't exist or a regular file. OK. */
+ return (r == 0) ? 1 : 0;
+}
+
+void load_hostkeys()
+{
+ char fsobj[BUFSIZE];
+ char linebuf[BUFSIZE];
+ FILE *filp;
+
+ if (!ensure_leading_directories())
+ return;
+
+ sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+ filp = fopen(fsobj, "r");
+ if (filp == NULL)
+ die("Can't open $HOME/.gits/hostkeys");
+
+ while (fgets(linebuf, BUFSIZE - 2, filp)) {
+ struct hostkey *h;
+ char *split;
+ h = xmalloc(sizeof(struct hostkey));
+ h->h_next = NULL;
+ h->h_line = xstrdup(linebuf);
+
+ if (*h->h_line && h->h_line[strlen(h->h_line) - 1] == '\n')
+ h->h_line[strlen(h->h_line) - 1] = '\0';
+
+ if (!*h->h_line || h->h_line[0] == '#')
+ goto not_valid;
+ split = strchr(h->h_line, ' ');
+ if (!split)
+ goto not_valid;
+
+ *split = '\0';
+ h->h_hostname = h->h_line;
+ h->h_hostkey = xstrdup(split + 1);
+ h->h_line = NULL;
+not_valid:
+ if (last_hostkey)
+ last_hostkey = last_hostkey->h_next = h;
+ else
+ first_hostkey = last_hostkey = h;
+ }
+ if (!feof(filp))
+ die("Error reading $HOME/.gits/hostkeys");
+ fclose(filp);
+}
+
+void save_hostkeys()
+{
+ char fsobj[BUFSIZE];
+ char fsobj2[BUFSIZE];
+ FILE *filp;
+ struct hostkey *host;
+
+ sprintf(fsobj, "%s/.gits/hostkeys.tmp", get_home());
+ sprintf(fsobj2, "%s/.gits/hostkeys", get_home());
+ filp = fopen(fsobj, "w");
+ if (filp == NULL)
+ die("Can't open $HOME/.gits/hostkeys.tmp");
+
+ for (host = first_hostkey; host; host = host->h_next) {
+ if (host->h_line) {
+ if (fprintf(filp, "%s\n", host->h_line) < 0)
+ die("hostkeys write error");
+ } else {
+ if (fprintf(filp, "%s %s\n", host->h_hostname,
+ host->h_hostkey) < 0)
+ die("hostkeys write error");
+ }
+ }
+
+ if (fclose(filp) < 0)
+ die("Hostkeys write error");
+
+ if (rename(fsobj, fsobj2) < 0)
+ die("Error renaming hostkeys");
+}
+
+void list_hostkeys()
+{
+ struct hostkey *host;
+ size_t longest_hostname = 0;
+
+ for (host = first_hostkey; host; host = host->h_next) {
+ size_t hostnamelen = 0;
+ hostnamelen = host->h_hostname ? strlen(host->h_hostname) : 0;
+ if (longest_hostname < hostnamelen)
+ longest_hostname = hostnamelen;
+ }
+
+ for (host = first_hostkey; host; host = host->h_next) {
+ size_t pad;
+ size_t i;
+ if (!host->h_hostname)
+ continue;
+ pad = longest_hostname + 1 - strlen(host->h_hostname);
+ printf("%s", host->h_hostname);
+ for (i = 0; i < pad; i++)
+ printf(" ");
+ printf("%s\n", host->h_hostkey);
+ }
+}
+
+void add_hostkey(const char *host, const char *key)
+{
+ struct hostkey *h;
+
+ for (h = first_hostkey; h; h = h->h_next) {
+ if (!h->h_hostname)
+ continue;
+ if (!strcmp(h->h_hostname, host))
+ die("Host %s already in hostkeys", host);
+ }
+
+ h = xmalloc(sizeof(struct hostkey));
+ h->h_next = NULL;
+ h->h_line = NULL;
+ h->h_hostname = xstrdup(host);
+ h->h_hostkey = xstrdup(key);
+
+ if (last_hostkey)
+ last_hostkey = last_hostkey->h_next = h;
+ else
+ first_hostkey = last_hostkey = h;
+}
+
+void update_hostkey(const char *host, const char *key)
+{
+ struct hostkey *h;
+
+ for (h = first_hostkey; h; h = h->h_next) {
+ if (!h->h_hostname)
+ continue;
+ if (!strcmp(h->h_hostname, host)) {
+ h->h_hostkey = xstrdup(key);
+ return;
+ }
+ }
+ die("Host %s not found in hostkeys", host);
+}
+
+void delete_hostkey(const char *host)
+{
+ struct hostkey *h;
+ struct hostkey *prev = NULL;
+
+ for (h = first_hostkey; h; h = h->h_next) {
+ if (!h->h_hostname)
+ continue;
+ if (!strcmp(h->h_hostname, host)) {
+ if (prev)
+ prev->h_next = h->h_next;
+ else
+ first_hostkey = h->h_next;
+ free(h);
+ return;
+ }
+ prev = h;
+ }
+ die("Host %s not found in hostkeys", host);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "syntax: %s list\n", argv[0]);
+ fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+ fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+ fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+ return 1;
+ }
+ if (!strcmp(argv[1], "--help"))
+ do_help();
+
+ if (!strcmp(argv[1], "list")) {
+ if (argc != 2) {
+ fprintf(stderr, "syntax: %s list\n", argv[0]);
+ return 1;
+ }
+
+ load_hostkeys();
+ list_hostkeys();
+ return 0;
+ } else if (!strcmp(argv[1], "add")) {
+ if (argc != 4) {
+ fprintf(stderr, "syntax: %s add <host> <key>\n",
+ argv[0]);
+ return 1;
+ }
+
+ load_hostkeys();
+ add_hostkey(argv[2], argv[3]);
+ save_hostkeys();
+ return 0;
+ } else if (!strcmp(argv[1], "update")) {
+ if (argc != 4) {
+ fprintf(stderr, "syntax: %s update <host> <key>\n",
+ argv[0]);
+ return 1;
+ }
+
+ load_hostkeys();
+ update_hostkey(argv[2], argv[3]);
+ save_hostkeys();
+ return 0;
+ } else if (!strcmp(argv[1], "delete")) {
+ if (argc != 3) {
+ fprintf(stderr, "syntax: %s delete <host>\n",
+ argv[0]);
+ return 1;
+ }
+
+ load_hostkeys();
+ delete_hostkey(argv[2]);
+ save_hostkeys();
+ return 0;
+ } else {
+ fprintf(stderr, "syntax: %s list\n", argv[0]);
+ fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+ fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+ fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+ return 1;
+ }
+}
diff --git a/git-over-tls/keypairs.c b/git-over-tls/keypairs.c
new file mode 100644
index 0000000..cc77217
--- /dev/null
+++ b/git-over-tls/keypairs.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "keypairs.h"
+#include "home.h"
+#include "certificate.h"
+#include <stdio.h>
+#include <limits.h>
+#include <gnutls/openpgp.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+ const char *username)
+{
+ char keypath[PATH_MAX + 1];
+ const char *home;
+ struct certificate cert;
+ int r;
+
+ home = get_home();
+
+ r = snprintf(keypath, PATH_MAX + 1, "%s/.gits/keys/%s", home,
+ username);
+ if (r < 0 || r > PATH_MAX) {
+ die("Username too long");
+ }
+
+ cert = parse_certificate(keypath, &r);
+ if (r) {
+ if (r == CERTERR_NOCERT)
+ return -1;
+ if (r == CERTERR_CANTREAD)
+ die_errno("Can't read keypair");
+ else
+ die("Can't read keypair: %s",
+ cert_parse_strerr(r));
+ }
+
+ r = gnutls_certificate_set_openpgp_keyring_mem(creds,
+ cert.public_key.data, cert.public_key.size,
+ GNUTLS_OPENPGP_FMT_RAW);
+ if (r < 0)
+ die("Can't load public key: %s", gnutls_strerror(r));
+
+ r = gnutls_certificate_set_openpgp_key_mem(creds, &cert.public_key,
+ &cert.private_key, GNUTLS_OPENPGP_FMT_RAW);
+ if (r < 0)
+ die("Can't load keypair: %s", gnutls_strerror(r));
+
+ return 0;
+}
diff --git a/git-over-tls/keypairs.h b/git-over-tls/keypairs.h
new file mode 100644
index 0000000..11f4ef7
--- /dev/null
+++ b/git-over-tls/keypairs.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _keypairs__h__included__
+#define _keypairs__h__included__
+
+#include <gnutls/openpgp.h>
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+ const char *username);
+
+#endif
--
1.6.6.102.gd6f8f.dirty
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of 2)
2010-01-13 13:19 [RFC 0/2] Git-over-TLS (gits://) client side support Ilari Liusvaara
2010-01-13 13:19 ` [RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2) Ilari Liusvaara
@ 2010-01-13 13:19 ` Ilari Liusvaara
2010-01-13 13:25 ` Alex Riesen
2010-01-13 13:39 ` [RFC 0/2] Git-over-TLS (gits://) client side support Nguyen Thai Ngoc Duy
2010-01-13 20:13 ` Edward Z. Yang
3 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
To: git
Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
git-over-tls/main.c | 460 ++++++++++
git-over-tls/{home.h => misc.c} | 12 +-
git-over-tls/{keypairs.h => misc.h} | 21 +-
git-over-tls/mkcert.c | 507 +++++++++++
git-over-tls/prompt.c | 100 +++
git-over-tls/prompt.h | 18 +
git-over-tls/srp_askpass.c | 90 ++
git-over-tls/{hostkey.h => srp_askpass.h} | 9 +-
git-over-tls/user.c | 1384 +++++++++++++++++++++++++++++
git-over-tls/user.h | 357 ++++++++
10 files changed, 2943 insertions(+), 15 deletions(-)
create mode 100644 git-over-tls/main.c
copy git-over-tls/{home.h => misc.c} (64%)
copy git-over-tls/{keypairs.h => misc.h} (50%)
create mode 100644 git-over-tls/mkcert.c
create mode 100644 git-over-tls/prompt.c
create mode 100644 git-over-tls/prompt.h
create mode 100644 git-over-tls/srp_askpass.c
copy git-over-tls/{hostkey.h => srp_askpass.h} (59%)
create mode 100644 git-over-tls/user.c
create mode 100644 git-over-tls/user.h
diff --git a/git-over-tls/main.c b/git-over-tls/main.c
new file mode 100644
index 0000000..a3c8f51
--- /dev/null
+++ b/git-over-tls/main.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "user.h"
+#include "srp_askpass.h"
+#include "keypairs.h"
+#include "hostkey.h"
+#include "connect.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <gnutls/gnutls.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+struct parsed_addr
+{
+ char *protocol; /* Protocol part */
+ char *user; /* User part, NULL if no user */
+ char *host; /* Hostname */
+ char *uhost; /* Unique host + port */
+ char *port; /* Port as string, NULL if no port */
+ char *path; /* Path part. */
+ char *vhost_header; /* vhost header to send */
+ unsigned short _port; /* Port as numeric. */
+};
+
+char *copy_alloc(const char *str, size_t len)
+{
+ char *copy;
+
+ copy = xmalloc(len + 1);
+ copy[len] = 0;
+ strncpy(copy, str, len);
+ return copy;
+}
+
+void append_uniq_address(char* buffer, struct parsed_addr* _addr)
+{
+ if (strchr(_addr->host, ':'))
+ strcat(buffer, "[");
+ strcat(buffer, _addr->host);
+ if (strchr(_addr->host, ':'))
+ strcat(buffer, "]");
+ if (_addr->port) {
+ strcat(buffer, ":");
+ strcat(buffer, _addr->port);
+ }
+}
+
+struct parsed_addr parse_address(const char *addr)
+{
+ struct parsed_addr _addr;
+ const char *proto_end;
+ const char *path_start;
+ const char *uhp_start;
+ const char *uhp_delim;
+ const char *orig_addr = addr;
+ size_t addrlen;
+
+ addrlen = strlen(addr);
+
+ proto_end = strchr(addr, ':');
+ if (!proto_end)
+ goto bad;
+
+ _addr.protocol = copy_alloc(addr, proto_end - addr);
+ if (strncmp(proto_end, "://", 3))
+ goto bad;
+
+ uhp_start = proto_end + 3;
+
+ /* Figure out the user if any. */
+ uhp_delim = strpbrk(uhp_start, "@[:/");
+
+ if (*uhp_delim == '@') {
+ _addr.user = copy_alloc(uhp_start,
+ uhp_delim - uhp_start);
+ uhp_start = uhp_delim + 1;
+ } else {
+ _addr.user = NULL;
+ }
+
+ /* Figure out host. */
+ if (*uhp_start == '[') {
+ uhp_delim = strpbrk(uhp_start, "]");
+ if (uhp_delim) {
+ _addr.host = copy_alloc(uhp_start + 1,
+ uhp_delim - uhp_start - 1);
+ if (uhp_delim[1] != ':' && uhp_delim[1] != '/')
+ goto bad;
+ uhp_start = uhp_delim + 1;
+ } else
+ goto bad;
+ } else {
+ uhp_delim = strpbrk(uhp_start, "[:/");
+ if (*uhp_delim == '[')
+ goto bad;
+ _addr.host = copy_alloc(uhp_start, uhp_delim - uhp_start);
+ uhp_start = uhp_delim;
+ }
+
+ path_start = strchr(uhp_start, '/');
+ if (!path_start)
+ goto bad;
+
+ _addr.path = copy_alloc(path_start, addrlen - (path_start - addr));
+
+ if (*uhp_start == ':')
+ _addr.port = copy_alloc(uhp_start + 1,
+ path_start - uhp_start - 1);
+ else
+ _addr.port = NULL;
+
+ if (!*_addr.host)
+ goto bad;
+
+ if (strcmp(_addr.protocol, "gits") && strcmp(_addr.protocol, "tls") &&
+ strcmp(_addr.protocol, "git")) {
+ die("Unknown protocol %s://", _addr.protocol);
+ }
+
+ if (!strcmp(_addr.protocol, "git") && _addr.user) {
+ die("git:// does not support users");
+ }
+
+ if (_addr.port) {
+ char *end;
+ unsigned long x;
+ x = strtoul(_addr.port, &end, 10);
+ if (*end)
+ goto bad;
+ if (x < 1 || x > 65535)
+ goto bad;
+ _addr._port = (unsigned short)x;
+ } else if (!strcmp(_addr.protocol, "gits")) {
+ _addr._port = 9418;
+ } else if (!strcmp(_addr.protocol, "git")) {
+ _addr._port = 9418;
+ } else if (!strcmp(_addr.protocol, "tls")) {
+ die("tls:// needs port specification");
+ }
+
+ if (_addr.port) {
+ /* 9 is for host=[]:\0 */
+ size_t vhost_len = 7 + strlen(_addr.host) +
+ strlen(_addr.port);
+ _addr.vhost_header = xmalloc(vhost_len);
+ _addr.uhost = xmalloc(vhost_len);
+ } else {
+ /* 8 is for host=[]\0 */
+ size_t vhost_len = 6 + strlen(_addr.host);
+ _addr.vhost_header = xmalloc(vhost_len);
+ _addr.uhost = xmalloc(vhost_len);
+ }
+
+ strcpy(_addr.vhost_header, "host=");
+ append_uniq_address(_addr.vhost_header, &_addr);
+
+ strcpy(_addr.uhost, "");
+ append_uniq_address(_addr.uhost, &_addr);
+
+ return _addr;
+bad:
+ die("Bad URL \"%s\"", orig_addr);
+ /* Can't come here. */
+ return _addr;
+}
+
+#define MODE_ALLOW_EOF 0
+#define MODE_HANDSHAKE 1
+
+static void traffic_loop(struct user *user, int mode)
+{
+ fd_set rfds;
+ fd_set wfds;
+ int failcode = 0;
+ struct timeval deadline;
+ int bound = 0;
+ int r;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ user_add_to_sets(user, &bound, &rfds, &wfds, &deadline);
+ if (bound == 0) {
+ failcode = user_get_failure(user);
+ if (failcode)
+ goto failed;
+ return;
+ }
+ r = select(bound, &rfds, &wfds, NULL, NULL);
+ if (r < 0 && errno != EINTR) {
+ die_errno("select() failed");
+ } else if (r < 0) {
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ }
+ user_service(user, &rfds, &wfds);
+ failcode = user_get_failure(user);
+ if (failcode)
+ goto failed;
+ return;
+failed:
+ if (failcode > 0 && mode == MODE_ALLOW_EOF)
+ return;
+ else if (failcode > 0) {
+ die("Expected more data, got connection closed");
+ } else {
+ const char *major;
+ const char *minor;
+
+ major = user_explain_failure(failcode);
+ minor = user_get_error(user);
+
+ if (minor)
+ die("Connection lost: %s (%s)", major, minor);
+ else
+ die("Connection lost: %s", major);
+ }
+}
+
+static int select_keypair(gnutls_certificate_credentials_t creds,
+ const char *username, int must_succeed)
+{
+ int ret;
+ ret = select_keypair_int(creds, username);
+ if (ret < 0 && must_succeed)
+ die("No keypair identity %s found", username);
+ return ret;
+}
+
+static gnutls_session_t session;
+
+static void preconfigure_tls(const char *username)
+{
+ int s;
+ gnutls_certificate_credentials_t creds;
+ int keypair_ok = 0;
+#ifndef DISABLE_SRP
+ const char *srp_password;
+ gnutls_srp_client_credentials_t srp_cred;
+ int kx[3];
+#endif
+
+ s = gnutls_global_init();
+ if (s < 0)
+ die("Can't initialize GnuTLS: %s", gnutls_strerror(s));
+
+ s = gnutls_certificate_allocate_credentials(&creds);
+ if (s < 0)
+ die("Can't allocate cert creds: %s", gnutls_strerror(s));
+
+ s = gnutls_init(&session, GNUTLS_CLIENT);
+ if (s < 0)
+ die("Can't allocate session: %s", gnutls_strerror(s));
+
+#ifndef DISABLE_SRP
+ s = gnutls_priority_set_direct (session, "NORMAL:+SRP-DSS:+SRP-RSA",
+ NULL);
+#else
+ s = gnutls_priority_set_direct (session, "NORMAL", NULL);
+#endif
+ if (s < 0)
+ die("Can't set priority: %s", gnutls_strerror(s));
+
+ if (username) {
+ if (!prefixcmp(username, "key-")) {
+ select_keypair(creds, username + 4, 1);
+ keypair_ok = 1;
+ } else
+ keypair_ok = (select_keypair(creds, username, 0)
+ >= 0);
+ }
+
+ s = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, creds);
+ if (s < 0)
+ die("Can't set creds: %s", gnutls_strerror(s));
+
+ if (keypair_ok)
+ goto no_srp;
+#ifndef DISABLE_SRP
+ if (username && !prefixcmp(username, "srp-"))
+ username = username + 4;
+ if (!username || !*username)
+ goto no_srp;
+
+ s = gnutls_srp_allocate_client_credentials(&srp_cred);
+ if (s < 0)
+ die("Can't allocate SRP creds: %s", gnutls_strerror(s));
+
+ s = 0;
+ srp_password = get_srp_password(username);
+ s = gnutls_srp_set_client_credentials(srp_cred, username, srp_password);
+ if (s < 0)
+ die("Can't set SRP creds: %s", gnutls_strerror(s));
+
+ s = gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred);
+ if (s < 0)
+ die("Can't use SRP creds: %s", gnutls_strerror(s));
+
+ /* GnuTLS doesn't seem to like to use SRP. Force it. */
+ kx[0] = GNUTLS_KX_SRP_DSS;
+ kx[1] = GNUTLS_KX_SRP_RSA;
+ kx[2] = 0;
+ s = gnutls_kx_set_priority(session, kx);
+ if (s < 0)
+ die("Can't force SRP: %s", gnutls_strerror(s));
+#endif
+no_srp:
+ ;
+}
+
+static void configure_tls(struct user *user, const char *hostname)
+{
+ user_configure_tls(user, session);
+
+ /* Wait for TLS connection to establish. */
+ while (!user_get_tls(user))
+ traffic_loop(user, MODE_HANDSHAKE);
+
+ check_hostkey(session, hostname);
+}
+
+#define MAX_REQUEST 8192
+const char *hexes = "0123456789abcdef";
+
+static void do_request(const char *arg, struct parsed_addr *addr,
+ int supress_ok)
+{
+ int fd;
+ struct user *dispatcher;
+ struct cbuffer *inbuf;
+ struct cbuffer *outbuf;
+ const char *major;
+ const char *minor;
+ char reqbuf[MAX_REQUEST + 4];
+ size_t reqsize;
+
+ preconfigure_tls(addr->user);
+
+ fd = connect_host(addr->host, addr->_port);
+
+ /* Create dispatcher with no time limit. */
+ dispatcher = user_create(fd, 65535);
+ if (!dispatcher)
+ die("Can't create connection context");
+ user_clear_deadline(dispatcher);
+
+ inbuf = user_get_red_in(dispatcher);
+ outbuf = user_get_red_out(dispatcher);
+ if (!strcmp(addr->protocol, "git")) {
+ ; /* Not protected. */
+ } else if (!strcmp(addr->protocol, "tls")) {
+ configure_tls(dispatcher, addr->uhost);
+ } else {
+ cbuffer_write(inbuf, (unsigned char*)"000cstarttls", 12);
+ while (1) {
+ char tmpbuf[9];
+ int s;
+ traffic_loop(dispatcher, MODE_HANDSHAKE);
+ s = cbuffer_peek(outbuf, (unsigned char*)tmpbuf, 8);
+ tmpbuf[8] = '\0';
+ if (s >= 0 && !strcmp(tmpbuf, "proceed\n"))
+ break;
+ if (user_red_out_eofd(dispatcher))
+ goto wait_eofd;
+ if (user_get_failure(dispatcher))
+ goto wait_failed;
+ }
+ configure_tls(dispatcher, addr->uhost);
+ }
+
+ reqsize = strlen(arg) + strlen(addr->path) + 3 +
+ strlen(addr->vhost_header);
+
+ if (reqsize > MAX_REQUEST)
+ die("Request too big to send");
+
+ memcpy(reqbuf + 4, arg, strlen(arg));
+ reqbuf[strlen(arg) + 4] = ' ';
+ memcpy(reqbuf + strlen(arg) + 5, addr->path, strlen(addr->path) + 1);
+ memcpy(reqbuf + strlen(arg) + 6 + strlen(addr->path),
+ addr->vhost_header, strlen(addr->vhost_header) + 1);
+
+ reqbuf[0] = hexes[((reqsize + 4) >> 12) & 0xF];
+ reqbuf[1] = hexes[((reqsize + 4) >> 8) & 0xF];
+ reqbuf[2] = hexes[((reqsize + 4) >> 4) & 0xF];
+ reqbuf[3] = hexes[(reqsize + 4) & 0xF];
+
+ cbuffer_write(inbuf, (unsigned char*)reqbuf, reqsize + 4);
+ while (cbuffer_used(outbuf))
+ traffic_loop(dispatcher, MODE_HANDSHAKE);
+ /* Ok, remote end has replied. */
+ printf("\n");
+ fflush(stdout);
+ user_set_red_io(dispatcher, 0, 1, -1);
+
+ while (!user_get_failure(dispatcher))
+ traffic_loop(dispatcher, MODE_ALLOW_EOF);
+ exit(0);
+
+wait_failed:
+ major = user_explain_failure(user_get_failure(dispatcher));
+ minor = user_get_error(dispatcher);
+
+ if (minor)
+ die("Connection lost: %s (%s)", major, minor);
+ else
+ die("Connection lost: %s", major);
+ exit(128);
+wait_eofd:
+ die("Expected response to starttls, server closed connection.");
+ exit(128);
+}
+
+int main(int argc, char **argv)
+{
+ struct parsed_addr paddr;
+ char buffer[8192];
+
+ if (argc < 3) {
+ die("Need two arguments");
+ }
+
+ paddr = parse_address(argv[2]);
+
+ if (!prefixcmp(argv[1], "--service=")) {
+ do_request(argv[1] + 10, &paddr, 1);
+ return 0;
+ }
+
+ while (1) {
+ char *cmd;
+
+ cmd = fgets(buffer, 8190, stdin);
+ if (cmd[strlen(cmd) - 1] == '\n')
+ cmd[strlen(cmd) - 1] = '\0';
+
+ if (!strcmp(cmd, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!*cmd) {
+ exit(0);
+ } else if (!prefixcmp(cmd, "connect ")) {
+ do_request(cmd + 8, &paddr, 0);
+ return 0;
+ } else
+ die("Unknown command %s", cmd);
+ }
+ return 0;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/misc.c
similarity index 64%
copy from git-over-tls/home.h
copy to git-over-tls/misc.c
index 133ee78..3d3e0b3 100644
--- a/git-over-tls/home.h
+++ b/git-over-tls/misc.c
@@ -5,9 +5,11 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-#ifndef _home__h__included__
-#define _home__h__included__
+#include "misc.h"
+#include <unistd.h>
+#include <errno.h>
-const char *get_home();
-
-#endif
+void force_close(int fd)
+{
+ while (close(fd) < 0 && errno != EBADF);
+}
diff --git a/git-over-tls/keypairs.h b/git-over-tls/misc.h
similarity index 50%
copy from git-over-tls/keypairs.h
copy to git-over-tls/misc.h
index 11f4ef7..140341f 100644
--- a/git-over-tls/keypairs.h
+++ b/git-over-tls/misc.h
@@ -5,12 +5,23 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-#ifndef _keypairs__h__included__
-#define _keypairs__h__included__
+#ifndef _misc__h__included__
+#define _misc__h__included__
-#include <gnutls/openpgp.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Forcibly close the file descriptor.
+ *
+ * Input
+ * fd The file descriptor.
+ */
+void force_close(int fd);
-int select_keypair_int(gnutls_certificate_credentials_t creds,
- const char *username);
+#ifdef __cplusplus
+}
+#endif
#endif
diff --git a/git-over-tls/mkcert.c b/git-over-tls/mkcert.c
new file mode 100644
index 0000000..597f942
--- /dev/null
+++ b/git-over-tls/mkcert.c
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "cbuffer.h"
+#include "home.h"
+#include "prompt.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/stat.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#include "run-command.h"
+#endif
+
+#define CERT_MAX 65536
+#define BUFSIZE 8192
+
+void ensure_leading_directories()
+{
+ struct stat s;
+ char fsobj[BUFSIZE];
+ int r;
+
+ sprintf(fsobj, "%s/.gits", get_home());
+ r = stat(fsobj, &s);
+ if (r < 0 && errno != ENOENT)
+ die_errno("Stat $HOME/.gits");
+ else if (r == 0 && !S_ISDIR(s.st_mode))
+ die("$HOME/.gits exists but is not a directory");
+ else if (r < 0) {
+ /* Need to create it. */
+ if (mkdir(fsobj, 0700) < 0)
+ die_errno("Create $HOME/.gits failed");
+ }
+ /* Otherwise, r == 0 && S_ISDIR(s), which is OK. */
+ sprintf(fsobj, "%s/.gits/keys", get_home());
+ r = stat(fsobj, &s);
+ if (r < 0 && errno != ENOENT)
+ die_errno("Stat $HOME/.gits/keys");
+ else if (r == 0 && !S_ISDIR(s.st_mode))
+ die("$HOME/.gits/keys exists but is not a directory");
+ else if (r < 0) {
+ /* Need to create it. */
+ if (mkdir(fsobj, 0700) < 0)
+ die_errno("Create $HOME/.gits/keys failed");
+ }
+}
+
+
+static int seal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,
+ const char *sealer)
+{
+ struct child_process child;
+ char **argv;
+ char *sealer_copy;
+ int splits = 0;
+ int escape = 0;
+ int ridx, widx, tidx;
+ int cleanup = 0;
+ const char *i;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ for (i = sealer; *i; i++) {
+ if (escape)
+ escape = 0;
+ else if (*i == '\\')
+ escape = 1;
+ else if (*i == ' ')
+ splits++;
+ }
+
+ argv = xmalloc((splits + 2) * sizeof(char*));
+ argv[splits + 1] = NULL;
+
+ sealer_copy = xstrdup(sealer);
+ argv[0] = sealer_copy;
+
+ ridx = 0;
+ widx = 0;
+ tidx = 1;
+ escape = 0;
+ while (sealer_copy[ridx]) {
+ if (escape) {
+ escape = 0;
+ sealer_copy[widx++] = sealer_copy[ridx++];
+ } else if (sealer_copy[ridx] == '\\') {
+ ridx++;
+ escape = 1;
+ } else if (sealer_copy[ridx] == ' ') {
+ sealer_copy[widx++] = '\0';
+ argv[tidx++] = sealer_copy + widx;
+ ridx++;
+ } else
+ sealer_copy[widx++] = sealer_copy[ridx++];
+ }
+ sealer_copy[widx] = '\0';
+
+ memset(&child, 0, sizeof(child));
+ child.argv = (const char**)argv;
+ child.in = -1;
+ child.out = -1;
+ child.err = 0;
+ if (start_command(&child))
+ return -1;
+ cleanup = 1;
+
+ while (1) {
+ int bound;
+ fd_set rf;
+ fd_set wf;
+ int r;
+
+ FD_ZERO(&rf);
+ FD_ZERO(&wf);
+ FD_SET(child.out, &rf);
+ if (cbuffer_used(unsealed))
+ FD_SET(child.in, &wf);
+ else
+ close(child.in);
+
+ if (cbuffer_used(sealed))
+ bound = ((child.out > child.in) ? child.out :
+ child.in) + 1;
+ else
+ bound = child.out + 1;
+
+ r = select(bound, &rf, &wf, NULL, NULL);
+ if (r < 0 && r != EINTR) {
+ perror("Select");
+ goto exit_error;
+ }
+ if (r < 0) {
+ FD_ZERO(&rf);
+ FD_ZERO(&wf);
+ perror("select");
+ }
+
+ if (FD_ISSET(child.out, &rf)) {
+ r = cbuffer_read_fd(sealed, child.out);
+ if (r < 0 && errno != EINTR && errno != EAGAIN) {
+ fprintf(stderr, "Read from sealer "
+ "failed: %s", strerror(errno));
+ goto exit_error;
+ }
+ if (r < 0 && errno == EAGAIN)
+ if (!cbuffer_free(sealed)) {
+ fprintf(stderr, "Keypair too big\n");
+ goto exit_error;
+ }
+ if (r < 0)
+ perror("read");
+ if (r == 0)
+ break;
+ }
+
+ if (FD_ISSET(child.in, &wf)) {
+ r = cbuffer_write_fd(unsealed, child.in);
+ if (r < 0 && errno == EPIPE) {
+ fprintf(stderr, "Sealer exited "
+ "unexpectedly\n");
+ goto exit_error;
+ }
+ if (r < 0 && errno != EINTR && errno != EAGAIN) {
+ fprintf(stderr, "Write to sealer "
+ "failed: %s", strerror(errno));
+ goto exit_error;
+ }
+ if (r < 0)
+ perror("write");
+ }
+ }
+
+ if (finish_command(&child)) {
+ cleanup = 0;
+ goto exit_error;
+ }
+
+ close(child.in);
+ close(child.out);
+
+ return 0;
+exit_error:
+ close(child.in);
+ close(child.out);
+ return -1;
+}
+
+static void append_member(struct cbuffer *cbuf, const char *filename)
+{
+ unsigned char backing[CERT_MAX];
+ struct cbuffer *content;
+ int fd;
+ size_t size = 0;
+ unsigned char buf[2];
+
+ content = cbuffer_create(backing, CERT_MAX);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ perror("open");
+ exit(1);
+ }
+ while (1) {
+ ssize_t r = cbuffer_read_fd(content, fd);
+ if (r < 0) {
+ if (errno == EAGAIN) {
+ if (!cbuffer_free(content)) {
+ fprintf(stderr, "Member too big.\n");
+ unlink("key.private.tmp");
+ unlink("key.public.tmp");
+ exit(1);
+ }
+ } else if (errno != EINTR) {
+ perror("read");
+ exit(1);
+ }
+ } else if (r == 0) {
+ break;
+ } else
+ size += r;
+ }
+ close(fd);
+
+ buf[0] = (unsigned char)((size >> 8) & 0xFF);
+ buf[1] = (unsigned char)((size) & 0xFF);
+ if (cbuffer_write(cbuf, buf, 2) < 0) {
+ fprintf(stderr, "Certificate too big (can't write member header).\n");
+ unlink("key.private.tmp");
+ unlink("key.public.tmp");
+ exit(1);
+ }
+ if (cbuffer_move(cbuf, content, size) < 0) {
+ fprintf(stderr, "Certificate too big (can't write member of %u "
+ "bytes).\n", size);
+ unlink("key.private.tmp");
+ unlink("key.public.tmp");
+ exit(1);
+ }
+
+ cbuffer_destroy(content);
+}
+
+
+static char *escape(char *s)
+{
+ char *ans;
+ int ridx = 0, widx = 0;
+
+ ans = xmalloc(2 * strlen(s) + 1);
+ while (s[ridx]) {
+ if (s[ridx] == '\\') {
+ ans[widx++] = '\\';
+ ans[widx++] = '\\';
+ ridx++;
+ } else if (s[ridx] == ' ') {
+ ans[widx++] = '\\';
+ ans[widx++] = ' ';
+ ridx++;
+ } else {
+ ans[widx++] = s[ridx++];
+ }
+ }
+ ans[widx] = '\0';
+ free(s);
+ return ans;
+}
+
+static int check_name(char* s)
+{
+ size_t x;
+ x = strspn(s, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+ if (!x || s[x]) {
+ return -1;
+ }
+ return 0;
+}
+
+static int check_comment(char* s)
+{
+ char *at;
+ at = strchr(s, '(');
+ if (at)
+ return -1;
+ at = strchr(s, '\\');
+ if (at)
+ return -1;
+ at = strchr(s, ')');
+ if (at)
+ return -1;
+ at = s;
+ while (*at >= 32 && *at <= 126)
+ at++;
+ if (*at)
+ return -1;
+ return 0;
+}
+
+static int check_email(char* s)
+{
+ size_t x;
+ char *at;
+ at = strchr(s, '@');
+ if (!at)
+ return -1;
+ x = strspn(s, ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+ if (s[x] != '@')
+ return -1;
+ if (!at[1])
+ return -1;
+ x = strspn(at + 1, ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+ if (at[x + 1])
+ return -1;
+ return 0;
+}
+
+#define BUFSIZE 8192
+
+void write_cert(int server_mode)
+{
+ unsigned char backing1[CERT_MAX];
+ unsigned char backing2[CERT_MAX];
+ unsigned char keytemp[CERT_MAX];
+ struct cbuffer *unsealed;
+ struct cbuffer *sealed;
+ unsigned char *buf = (unsigned char*)"GITSUCERT";
+ unsigned char *buf2 = (unsigned char*)"GITSSCERT\x00\x03gpg";
+ char *name;
+ char fbuffer[BUFSIZE];
+ int fd;
+ int do_seal = 0;
+ int keylen = 1024;
+ char *realname;
+ char *comment;
+ char *email;
+ FILE *script;
+
+ if (!server_mode)
+ ensure_leading_directories();
+
+ unsealed = cbuffer_create(backing1, CERT_MAX);
+ sealed = cbuffer_create(backing2, CERT_MAX);
+
+reask_length:
+ printf("1) 1024 bit key\n");
+ printf("2) 2048 bit key\n");
+ printf("3) 3072 bit key\n");
+ name = prompt_string("Pick key length", 0);
+ if (!strcmp(name, "1"))
+ keylen = 1024;
+ else if (!strcmp(name, "2"))
+ keylen = 2048;
+ else if (!strcmp(name, "3"))
+ keylen = 3072;
+ else {
+ fprintf(stderr, "Bad choice\n");
+ goto reask_length;
+ }
+
+ask_name:
+ realname = prompt_string("Enter name to put into key", 0);
+ if (check_name(realname) < 0) {
+ fprintf(stderr, "Bad name\n");
+ goto ask_name;
+ }
+ask_comment:
+ comment = prompt_string("Enter comment to put into key", 0);
+ if (check_comment(comment) < 0) {
+ fprintf(stderr, "Bad comment\n");
+ goto ask_comment;
+ }
+ask_email:
+ email = prompt_string("Enter E-mail address to put into key", 0);
+ if (check_email(email) < 0) {
+ fprintf(stderr, "Bad E-mail address\n");
+ goto ask_email;
+ }
+
+ script = fopen("key.script.tmp", "w");
+ if (!script)
+ die("Can't create key script");
+ fprintf(script, "Key-Type: DSA\n");
+ fprintf(script, "Key-Length: %i\n", keylen);
+ fprintf(script, "Name-Real: %s\n", realname);
+ if (*comment)
+ fprintf(script, "Name-Comment: %s\n", comment);
+ fprintf(script, "Name-Email: %s\n", email);
+ fprintf(script, "Expire-Date: 0\n");
+ fprintf(script, "%%pubring key.public.tmp\n");
+ fprintf(script, "%%secring key.private.tmp\n");
+ fprintf(script, "%%commit\n");
+ fprintf(script, "%%echo done\n");
+ fclose(script);
+
+ if (system("gpg --batch --gen-key key.script.tmp")) {
+ unlink("key.private.tmp");
+ unlink("key.public.tmp");
+ unlink("key.script.tmp");
+ die("Can't generate key");
+ }
+ unlink("key.script.tmp");
+
+ append_member(unsealed, "key.private.tmp");
+ unlink("key.private.tmp");
+ append_member(unsealed, "key.public.tmp");
+ unlink("key.public.tmp");
+
+reask_seal:
+ if (server_mode)
+ goto no_seal;
+ printf("1) Don't seal key\n");
+ printf("2) Seal using password (gpg)\n");
+ printf("3) Seal using keypair (gpg)\n");
+ name = prompt_string("Pick sealing method", 0);
+ if (!strcmp(name, "1"))
+ do_seal = 0;
+ else if (!strcmp(name, "2"))
+ do_seal = 1;
+ else if (!strcmp(name, "3"))
+ do_seal = 2;
+ else {
+ fprintf(stderr, "Bad choice");
+ goto reask_seal;
+ }
+no_seal:
+ keylen = cbuffer_read_max(unsealed, keytemp, CERT_MAX);
+ cbuffer_write(unsealed, keytemp, keylen);
+
+ if (do_seal == 1) {
+ cbuffer_write(sealed, (unsigned char*)buf2, 14);
+ if (seal_cert(sealed, unsealed, "gpg --symmetric "
+ "--force-mdc") < 0) {
+ cbuffer_clear(sealed);
+ cbuffer_clear(unsealed);
+ cbuffer_write(unsealed, keytemp, keylen);
+ fprintf(stderr, "Sealing failed.\n");
+ goto reask_seal;
+ }
+ } else if (do_seal == 2) {
+ char* hint;
+ cbuffer_write(sealed, (unsigned char*)buf2, 14);
+
+ hint = prompt_string("Seal using whose key", 0);
+ hint = escape(hint);
+ sprintf(fbuffer, "gpg --encrypt --recipient %s "
+ "--force-mdc", hint);
+ free(hint);
+
+ if (seal_cert(sealed, unsealed, fbuffer) < 0) {
+ cbuffer_clear(sealed);
+ cbuffer_clear(unsealed);
+ cbuffer_write(unsealed, keytemp, keylen);
+ fprintf(stderr, "Sealing failed.\n");
+ goto reask_seal;
+ }
+ } else {
+ cbuffer_write(sealed, (unsigned char*)buf, 9);
+ cbuffer_move_nolimit(sealed, unsealed);
+ if (!cbuffer_free(sealed))
+ die("Key too large");
+ }
+
+retry_name:
+ if (server_mode) {
+ name = prompt_string("Enter filename to save key as", 0);
+ strcpy(fbuffer, name);
+ } else {
+ name = prompt_string("Enter name for key", 0);
+ if (strcspn(name, "@:/[") < strlen(name)) {
+ fprintf(stderr, "Bad name\n");
+ goto retry_name;
+ }
+ sprintf(fbuffer, "%s/.gits/keys/%s", get_home(), name);
+ }
+ if (!strcmp(name, "")) {
+ fprintf(stderr, "Bad name\n");
+ goto retry_name;
+ }
+
+ fd = open(fbuffer, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (fd < 0) {
+ fprintf(stderr, "Can't open %s: %s\n", fbuffer,
+ strerror(errno));
+ goto retry_name;
+ }
+ while (cbuffer_used(sealed)) {
+ ssize_t r = cbuffer_write_fd(sealed, fd);
+ if (r < 0 && errno != EINTR) {
+ perror("write");
+ exit(1);
+ }
+ }
+ close(fd);
+}
diff --git a/git-over-tls/prompt.c b/git-over-tls/prompt.c
new file mode 100644
index 0000000..380156a
--- /dev/null
+++ b/git-over-tls/prompt.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "prompt.h"
+#include <termios.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static int tmpfd;
+
+static void sigint(int x);
+
+void echo_off(int fd)
+{
+ struct termios t;
+
+ if (tcgetattr(fd, &t) < 0)
+ die_errno("Can't read terminal settings");
+
+ t.c_lflag &= ~ECHO;
+
+ tmpfd = fd;
+ signal(SIGINT, sigint);
+
+ if (tcsetattr(fd, TCSANOW, &t) < 0)
+ die_errno("Can't write terminal settings");
+}
+
+void echo_on(int fd)
+{
+ struct termios t;
+
+ if (tcgetattr(fd, &t) < 0)
+ die_errno("Can't read terminal settings");
+
+ t.c_lflag |= ECHO;
+
+ if (tcsetattr(fd, TCSANOW, &t) < 0)
+ die_errno("Can't write terminal settings");
+
+ signal(SIGINT, SIG_DFL);
+}
+
+static void sigint(int x)
+{
+ echo_on(tmpfd);
+ exit(1);
+}
+
+#define PROMPTBUF 8192
+
+char *prompt_string(const char *prompt, int without_echo)
+{
+ char ansbuf[8192];
+ char *ans;
+ int fd;
+ FILE* tty;
+
+ fd = open("/dev/tty", O_RDWR);
+ if (fd < 0)
+ die_errno("Can't open /dev/tty for password prompt");
+
+ tty = xfdopen(fd, "r+");
+
+ fprintf(tty, "%s: ", prompt);
+ fflush(tty);
+ if (without_echo)
+ echo_off(fd);
+
+ if (!fgets(ansbuf, 8190, tty)) {
+ if (without_echo)
+ echo_on(fd);
+ die("Can't read answer");
+ }
+
+ if (without_echo) {
+ fprintf(tty, "\n");
+ echo_on(fd);
+ }
+
+ if (*ansbuf && ansbuf[strlen(ansbuf) - 1] == '\n')
+ ansbuf[strlen(ansbuf) - 1] = '\0';
+
+ fclose(tty);
+
+ ans = xstrdup(ansbuf);
+ return ans;
+}
diff --git a/git-over-tls/prompt.h b/git-over-tls/prompt.h
new file mode 100644
index 0000000..34fc13a
--- /dev/null
+++ b/git-over-tls/prompt.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _prompt__h__included__
+#define _prompt__h__included__
+
+/* Turn terminal echo on specified fd off. */
+void echo_off(int fd);
+/* Turn terminal echo on specified fd on. */
+void echo_on(int fd);
+/* Prompt string from user and return mallocced copy of it. */
+char *prompt_string(const char *prompt, int without_echo);
+
+#endif
diff --git a/git-over-tls/srp_askpass.c b/git-over-tls/srp_askpass.c
new file mode 100644
index 0000000..0c0da36
--- /dev/null
+++ b/git-over-tls/srp_askpass.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "srp_askpass.h"
+#include "prompt.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#define PROMPTBUF 8192
+#define CMDBUFSIZE 16384
+
+/* Use GITS_ASKPASS to ask for password. */
+static char *get_password_via_external(const char *username,
+ const char *prog)
+{
+ static char buffer[PROMPTBUF + 1];
+ static char cmdbuffer[CMDBUFSIZE + 1];
+ char *ans;
+ int escape = 0;
+ int idx;
+ int len;
+ int widx = 0;
+ FILE *out;
+
+ if (strchr(username, '\"'))
+ die("Can't prompt for usernames containing '\"'");
+
+ len = snprintf(buffer, PROMPTBUF + 1, "\"Enter SRP password for %s\"",
+ username);
+ if (len < 0 || len > PROMPTBUF)
+ die("SRP Username is insanely long");
+
+ for (idx = 0; prog[idx]; idx++) {
+ if (!escape && prog[idx] == '%')
+ escape = 1;
+ else if (escape && prog[idx] == 'p') {
+ if (widx + strlen(buffer) >= CMDBUFSIZE)
+ die("Command line too long");
+ strcpy(cmdbuffer + widx, buffer);
+ widx += strlen(buffer);
+ } else {
+ if (widx + 1 >= CMDBUFSIZE)
+ die("Command line too long");
+ cmdbuffer[widx++] = prog[idx];
+ escape = 0;
+ }
+ }
+ cmdbuffer[widx++] = '\0';
+
+ out = popen(cmdbuffer, "r");
+ if (!out)
+ die_errno("Can't invoke $GITS_ASKPASS");
+
+ if (!fgets(buffer, PROMPTBUF - 2, out)) {
+ die("Can't read password");
+ }
+
+ if (strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\n')
+ buffer[strlen(buffer) - 1] = '\0';
+
+ if (pclose(out))
+ die("Authentication canceled");
+
+ ans = xstrdup(buffer);
+ return ans;
+}
+
+char *get_srp_password(const char *username)
+{
+ static char buffer[PROMPTBUF + 1];
+
+ if (getenv("GITS_ASKPASS"))
+ return get_password_via_external(username,
+ getenv("GITS_ASKPASS"));
+
+ sprintf(buffer, "Enter SRP password for %s", username);
+ return prompt_string(buffer, 1);
+}
diff --git a/git-over-tls/hostkey.h b/git-over-tls/srp_askpass.h
similarity index 59%
copy from git-over-tls/hostkey.h
copy to git-over-tls/srp_askpass.h
index c0e7dfe..d7271fd 100644
--- a/git-over-tls/hostkey.h
+++ b/git-over-tls/srp_askpass.h
@@ -5,11 +5,10 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-#ifndef _hostkey__h__included__
-#define _hostkey__h__included__
+#ifndef _srp_askpass__h__included__
+#define _srp_askpass__h__included__
-#include <gnutls/gnutls.h>
-
-void check_hostkey(gnutls_session_t session, const char *hostname);
+/* Get SRP password. Return is malloced */
+char *get_srp_password(const char *username);
#endif
diff --git a/git-over-tls/user.c b/git-over-tls/user.c
new file mode 100644
index 0000000..2bb23f5
--- /dev/null
+++ b/git-over-tls/user.c
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "user.h"
+#include "cbuffer.h"
+#include "misc.h"
+#include <sys/select.h>
+#include <sys/time.h>
+#include <gnutls/gnutls.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#ifdef USE_TRAP_PAGING
+#include <sys/mman.h>
+#endif
+
+/*
+ * NOTE: This code may not crash, call exit or anything similar unless
+ * program state is corrupt or call parameters are completely invalid.
+ * It also may not call Git APIs.
+ */
+
+/* Main buffer size. */
+#define BUFFERSIZE 65536
+/*
+ * Error buffer can be maximum of 65535-8 bytes and must be smaller or
+ * equal in size to main buffers.
+ */
+#define ERR_BUFFERSIZE (65535-8)
+
+struct user
+{
+ /* If 1, EOF received from black in at transport level. */
+ unsigned u_black_in_eof : 1,
+ /* If 1, EOF sent to black out at transport level. */
+ u_black_out_eof : 1,
+ /* If 1, EOF received from black in at TLS level. */
+ u_black_in_d_eof : 1,
+ /* If 1, EOF sent to black out at TLS level. */
+ u_black_out_d_eof : 1,
+ /* If 1, Assume that there is more input to come from red in. */
+ u_red_assume_more : 1,
+ /* If 1, TLS is active and ready to transfer data. */
+ u_tls_active : 1,
+ /* If 1, there has been data received from red in. */
+ u_red_in_have_data : 1,
+ /*
+ * Result of last read of decrypted data.
+ * 0 => Read was successful or not attempted yet.
+ * 1 => Read was blocked by insufficient input data.
+ * 2 => Read was blocked by insufficient output space.
+ * 3 => (Reserved)
+ */
+ u_want_read : 2,
+ /*
+ * Result of last write of decrypted data.
+ * 0 => Write was successful or not attempted yet.
+ * 1 => Write was blocked by insufficient input data.
+ * 2 => Write was blocked by insufficient output space.
+ * 3 => (Reserved)
+ */
+ u_want_write : 2,
+ /*
+ * Result of last handshake attempt.
+ * 0 => Handshake was successful or not attempted yet.
+ * 1 => Handshake was blocked by insufficient input data.
+ * 2 => Handshake was blocked by insufficient output space.
+ * 3 => (Reserved)
+ */
+ u_want_hand : 2,
+ /* Number of bytes of error header sent (0-8). */
+ u_red_err_hdr_sent : 4,
+ /* Delay TLS failure present. 1 for handshake, 2 otherwise */
+ u_delay_tls_failure : 2,
+ /* Has seen any data received. */
+ u_seen_input_data : 1;
+
+ /* File descriptors. -1 if none. */
+ int u_black_fd; /* Input/Output */
+ int u_red_in_fd; /* Input */
+ int u_red_out_fd; /* Output */
+ int u_red_err_fd; /* Input */
+ /* The backing buffer for all transfer buffers and its size. */
+ unsigned char *u_buf_backing;
+ size_t u_buf_backing_size;
+ /* The actual transfer buffers. */
+ struct cbuffer *u_black_in_buf;
+ struct cbuffer *u_black_out_buf;
+ struct cbuffer *u_red_in_buf;
+ struct cbuffer *u_red_out_buf;
+ struct cbuffer *u_red_err_buf;
+ /* Deadline. Tv_sec is -1 if there is no deadline. */
+ struct timeval u_deadline;
+ /*
+ * Why the connection was torn down. Delay_failure is delayed
+ * failure that becomes real after output buffer is flushed
+ * so that TLS alerts are sent. Also stored is delayed TLS alert
+ * that couldn't be sent yet.
+ */
+ int u_failure;
+ char *u_errmsg;
+ int u_delay_failure;
+ gnutls_alert_description_t u_delay_alert;
+ /* Active TLS session. NULL if no TLS. */
+ gnutls_session_t u_tls_session;
+};
+
+/* Initiate delayed failure on user. */
+static void delay_cleanup_user(struct user *user, int failure,
+ const char *error)
+{
+ /* Store the cause of termination. */
+ if (!user->u_errmsg) {
+ if (error)
+ user->u_errmsg = strdup(error);
+ else
+ user->u_errmsg = NULL;
+ }
+ user->u_delay_failure = failure;
+}
+
+/* Clean up user connection immediately. */
+static void cleanup_user(struct user *user, int failure, const char *error)
+{
+ /* If there is TLS session, deallocate its resources. */
+ if (user->u_tls_session) {
+ gnutls_deinit(user->u_tls_session);
+ user->u_tls_session = NULL;
+ }
+ /* Shutdown and close black input/output. */
+ if (user->u_black_fd >= 0) {
+ shutdown(user->u_black_fd, SHUT_WR);
+ force_close(user->u_black_fd);
+ }
+ user->u_black_fd = -1;
+
+ /* Close red inputs/outputs. */
+ if (user->u_red_in_fd >= 0)
+ force_close(user->u_red_in_fd);
+ user->u_red_in_fd = -1;
+ if (user->u_red_out_fd >= 0)
+ force_close(user->u_red_out_fd);
+ user->u_red_out_fd = -1;
+ if (user->u_red_err_fd >= 0)
+ force_close(user->u_red_err_fd);
+ user->u_red_err_fd = -1;
+
+ /* Store the cause of termination. */
+ if (!user->u_errmsg) {
+ if (error)
+ user->u_errmsg = strdup(error);
+ else
+ user->u_errmsg = NULL;
+ }
+ user->u_failure = failure;
+}
+
+/*
+ * Process delay failure. If there is delayed failure and output buffer is
+ * empty, make it real failure and disconnect user immediately.
+ */
+static int process_delay_failure(struct user *user)
+{
+ if (user->u_failure)
+ return 0;
+ if (!cbuffer_used(user->u_black_out_buf) && user->u_delay_failure) {
+ cleanup_user(user, user->u_delay_failure, NULL);
+ return 0;
+ }
+ return 0;
+}
+
+/* Is this error from I/O syscall fatal? */
+static int is_fatal_error(int error)
+{
+ return (error != EINTR && error != EAGAIN && error != EWOULDBLOCK);
+}
+
+/* Is this error from GnuTLS I/O operation fatal? */
+static int is_fatal_tls_error(int error)
+{
+ /*
+ * GNUTLS_E_INTERRUPTED should never be seen. since custom push and
+ * pull functions can't return EINTR, only EAGAIN.
+ */
+ return (error < 0 && error != GNUTLS_E_AGAIN);
+}
+
+/* Handle fatal error received from GnuTLS functions. */
+static int handle_tls_failure_code(struct user *user, int gnutls_code,
+ int handshaking)
+{
+ int x;
+ char error_buffer[8192];
+ if (gnutls_code == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+ /* Fatal alert. Get the description and format message. */
+ gnutls_alert_description_t alert;
+ alert = gnutls_alert_get(user->u_tls_session);
+ sprintf(error_buffer, "TLS alert received: %s",
+ gnutls_alert_get_name(alert));
+
+ if (alert == GNUTLS_A_BAD_RECORD_MAC && handshaking)
+ sprintf(error_buffer, "TLS alert received: %s "
+ "(incorrect password?)",
+ gnutls_alert_get_name(alert));
+
+ /* Terminate the connection. */
+ if (handshaking)
+ cleanup_user(user, USER_TLS_HAND_ERROR,
+ error_buffer);
+ else
+ cleanup_user(user, USER_TLS_ERROR,
+ error_buffer);
+ return 0;
+ }
+ /*
+ * Alerts use handshake readyness indicator, so try to set it
+ * to "perform I/O immediately".
+ */
+ user->u_want_hand = 0;
+ /*
+ * Mark that delay TLS failure is present and get the alert
+ * that should be sent. Also set error message.
+ */
+ user->u_delay_tls_failure = 1;
+#ifndef DISABLE_SRP
+ /* GnuTLS doesn't seem to have proper code for this. */
+ if (gnutls_code == GNUTLS_E_SRP_PWD_ERROR)
+ user->u_delay_alert = GNUTLS_A_UNKNOWN_PSK_IDENTITY;
+ else
+#endif
+ user->u_delay_alert = (gnutls_alert_description_t)
+ gnutls_error_to_alert((int)gnutls_code, &x);
+ user->u_errmsg = strdup(gnutls_strerror_name((int)gnutls_code));
+ return 1;
+}
+
+/* Handle black input activity */
+static int black_in_handler(struct user *user, fd_set *rfds)
+{
+ /* Don't attempt to read if connection has failed. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* The file descrptor must be marked readable. */
+ if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, rfds))
+ return 0;
+ /* Don't attempt to read already EOF'd file descriptor. */
+ if (user->u_black_in_eof)
+ return 0;
+
+ ssize_t r = cbuffer_read_fd(user->u_black_in_buf, user->u_black_fd);
+ FD_CLR(user->u_black_fd, rfds);
+ if (r < 0 && is_fatal_error(errno)) {
+ /* Inbound connection faulted! */
+ cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+ } else if (r == 0) {
+ /* Received EOF. */
+ user->u_black_in_eof = 1;
+ } else if (r > 0) {
+ /* Received some data. */
+ user->u_seen_input_data = 1;
+ }
+ return 1;
+}
+
+/* Handle black output activity. */
+static int black_out_handler(struct user *user, fd_set *wfds)
+{
+ /*
+ * Don't attempt to write if connection has failed. This is one of
+ * the very few handlers still to run in delayed failure.
+ */
+ if (user->u_failure)
+ return 0;
+ /* The file descrptor must be marked as writable. */
+ if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, wfds))
+ return 0;
+ /* Don't attempt to write if EOF has already been sent. */
+ if (user->u_black_out_eof)
+ return 0;
+
+ ssize_t r = cbuffer_write_fd(user->u_black_out_buf, user->u_black_fd);
+ FD_CLR(user->u_black_fd, wfds);
+ if (r < 0 && is_fatal_error(errno)) {
+ /* Outbound connection faulted! */
+ cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+ } else if (r > 0) {
+ /* Sent some data. */
+ }
+ return 1;
+}
+
+/* Send black out EOF if needed (using TLS if it is active) */
+static int black_out_eof_handler(struct user *user)
+{
+ /* Don't operate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate anymore if EOF has been sent outbound. */
+ if (user->u_black_out_eof || user->u_black_out_d_eof)
+ return 0;
+ /* Don't activate if there can be more data from red in. */
+ if (user->u_red_assume_more || user->u_red_in_fd >= 0)
+ return 0;
+ /* Don't activate if there can be more data from red error. */
+ if (user->u_red_err_fd >= 0)
+ return 0;
+ /* Don't activate if there is more data in red input. */
+ if (cbuffer_used(user->u_red_in_buf))
+ return 0;
+ /* Don't activate if there is more data in red error. */
+ if (cbuffer_used(user->u_red_err_buf))
+ return 0;
+ /*
+ * If not in TLS mode, don't activate if there is data in send
+ * buffer.
+ */
+ if (!user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+ return 0;
+
+ if (user->u_tls_session) {
+ int r;
+ /* Try to send the EOF at TLS level. */
+ r = gnutls_bye(user->u_tls_session, GNUTLS_SHUT_WR);
+ if (r == 0) {
+ /* Suceeded. Mark the connection as EOF'd. */
+ user->u_black_out_d_eof = 1;
+ return 1;
+ } else if (is_fatal_tls_error((int)r)) {
+ /* Fatal stream error! */
+ return handle_tls_failure_code(user, (int)r, 0);
+ }
+ /* Otherwise we failed due to non-fatal TLS error. We'll try
+ again soon. */
+ return 0;
+ } else {
+ /* Try to send normal transport EOF. */
+ if (shutdown(user->u_black_fd, SHUT_WR) < 0) {
+ /* Failed, declare as stream error. */
+ cleanup_user(user, USER_LAYER4_ERROR,
+ strerror(errno));
+ return 0;
+ }
+ user->u_black_out_eof = 1;
+ return 1;
+ }
+ return 0; /* Should not be here. */
+}
+
+/* Send EOF to red output if needed. */
+static int red_out_eof_handler(struct user *user)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate unless input EOF has been received. */
+ if (!user->u_black_in_eof && !user->u_black_in_d_eof)
+ return 0;
+ /* Don't activate anymore if red out has been closed. */
+ if (user->u_red_out_fd < 0)
+ return 0;
+ /* If not in TLS mode, don't activate if there is data in receive
+ buffer. */
+ if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+ return 0;
+ /* Don't activate if there is more data in red output. */
+ if (cbuffer_used(user->u_red_out_buf))
+ return 0;
+
+ force_close(user->u_red_out_fd);
+ user->u_red_out_fd = -1;
+ return 1;
+}
+
+/* Send data to file descriptor connected to red output. */
+static int red_out_handler(struct user *user, fd_set *wfds)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Red out must be writable fd. */
+ if (user->u_red_out_fd < 0 || !FD_ISSET(user->u_red_out_fd, wfds))
+ return 0;
+
+ ssize_t r = cbuffer_write_fd(user->u_red_out_buf, user->u_red_out_fd);
+ FD_CLR(user->u_red_out_fd, wfds);
+ if (r < 0 && errno == EPIPE) {
+ /* EPIPE is treated as special type of EOF. We need to read
+ EOFs from process. */
+ force_close(user->u_red_out_fd);
+ user->u_red_out_fd = -1;
+ return 1;
+ } else if (r < 0 && is_fatal_error(errno)) {
+ /* Red out connection faulted! */
+ cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+ return 0;
+ } else if (r > 0) {
+ /* Sent some data to red output. */
+ return 1;
+ }
+ /* Can't come here. */
+ return 0;
+}
+
+/* Recieve data from file descriptor connected to red input. */
+static int red_in_handler(struct user *user, fd_set *rfds)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Red in must be readable fd. */
+ if (user->u_red_in_fd < 0 || !FD_ISSET(user->u_red_in_fd, rfds))
+ return 0;
+
+ ssize_t r = cbuffer_read_fd(user->u_red_in_buf, user->u_red_in_fd);
+ FD_CLR(user->u_red_in_fd, rfds);
+ if (r < 0 && is_fatal_error(errno)) {
+ /* Inbound red connection faulted! */
+ cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+ return 0;
+ } else if (r == 0) {
+ /* Close it so we eventually send EOF. */
+ user->u_red_assume_more = 0;
+ force_close(user->u_red_in_fd);
+ user->u_red_in_fd = -1;
+ return 1;
+ } else if (r > 0) {
+ /* Received some data. */
+ user->u_red_in_have_data = 1;
+
+ /* Clear the error buffer. */
+ cbuffer_clear(user->u_red_err_buf);
+ }
+ return 1;
+}
+
+#define DUMMYBUFSIZE 256
+
+/* Receive data from file descriptor connected to red error. */
+static int red_err_handler(struct user *user, fd_set *rfds)
+{
+ ssize_t r;
+ char buf[DUMMYBUFSIZE];
+
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Red err must be readable fd. */
+ if (user->u_red_err_fd < 0 || !FD_ISSET(user->u_red_err_fd, rfds))
+ return 0;
+
+ if (!user->u_red_in_have_data) {
+ /* Read the red error for real. */
+ r = cbuffer_read_fd(user->u_red_err_buf, user->u_red_err_fd);
+ } else {
+ /* Just do dummy read and discard the reuslts. */
+ r = read(user->u_red_err_fd, buf, DUMMYBUFSIZE);
+ }
+ FD_CLR(user->u_red_err_fd, rfds);
+ if (r < 0 && is_fatal_error(errno)) {
+ /* Inbound red connection faulted! */
+ cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+ } else if (r == 0) {
+ /* Close it so we eventually send EOF. */
+ force_close(user->u_red_err_fd);
+ user->u_red_err_fd = -1;
+ } else if (r > 0) {
+ /*
+ * Received some data.
+ *
+ * HACK ALERT: Some systems seem to like to send these
+ * non-errors on error channel. Ignore those and treat
+ * them like data from stdout. (35 is length of that
+ * string).
+ */
+ const char* tmsg = "Initialized empty Git repository in";
+ unsigned char tmp[35];
+ if (cbuffer_peek(user->u_red_err_buf, tmp, 35) >= 0 &&
+ !strncmp(tmsg, (char*)tmp, 35)) {
+ user->u_red_in_have_data = 1;
+ cbuffer_clear(user->u_red_err_buf);
+ }
+ }
+ return 1;
+}
+
+/* Terminate the whole connection if needed EOFs have been seen. */
+static int connection_eof_handler(struct user *user)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate if red out is still open. */
+ if (user->u_red_out_fd >= 0)
+ return 0;
+ /* Don't activate unless output EOF has been asserted. */
+ if (!user->u_black_out_eof && !user->u_black_out_d_eof)
+ return 0;
+ /* Don't assert in TLS mode unless output buffer is empty. */
+ if (user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+ return 0;
+
+ /* Both half-connections have ended. End the entiere connection. */
+ cleanup_user(user, USER_CONNECTION_END, NULL);
+ return 1;
+}
+
+/*
+ * Should operation take place given last operation failed practicular way?
+ * Direction2 is 0 if last operation didn't fail, 1 if it failed due to
+ * insufficient data to read, 2 if it failed due to insufficient space to
+ * write.
+ */
+static int gnutls_blacks_activate2(struct user *user, int direction2)
+{
+ /*
+ * If not attempted yet, or last time it was successful, attempt
+ * immediately again.
+ */
+ if (direction2 == 0)
+ return 1;
+ /*
+ * If last time it failed due to insufficient read space, try
+ * again only if there is some data in read buffers now.
+ */
+ if (direction2 == 1) {
+ if (cbuffer_used(user->u_black_in_buf) != 0)
+ return 1;
+ else if (!user->u_black_in_eof)
+ return 0;
+ else if (user->u_tls_session && user->u_seen_input_data)
+ cleanup_user(user, USER_TLS_ERROR, "Connection "
+ "closed unexpectedly");
+ else if (user->u_tls_session)
+ cleanup_user(user, USER_LAYER4_ERROR, "Connection "
+ "broke before any data was received");
+ else
+ return 0;
+ }
+ /*
+ * If last time it failed due to insufficient write space, try
+ * again only if there is some space in write buffers now.
+ */
+ if (direction2 == 2)
+ return (cbuffer_free(user->u_black_out_buf) != 0);
+ return 0;
+}
+
+/* Copy or decrypt data from black input to red output. */
+static int black_to_red_handler(struct user *user)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate if there's TLS but it isn't ready yet. */
+ if (user->u_tls_session && !user->u_tls_active)
+ return 0;
+ /* Don't activate if there's no space in red out buffer. */
+ if (!cbuffer_free(user->u_red_out_buf))
+ return 0;
+ /* Don't activate if TLS-level EOF has been received. */
+ if (user->u_black_in_d_eof)
+ return 0;
+ /* Check that we don't fail like last time. */
+ if (!gnutls_blacks_activate2(user, user->u_want_read))
+ return 0;
+
+ if (!user->u_tls_session) {
+ /*
+ * no-TLS case. Just compute how much data we can move and
+ * then just move it from black in buffer to red out buffer.
+ */
+ size_t amount;
+ amount = cbuffer_move_nolimit(user->u_red_out_buf,
+ user->u_black_in_buf);
+ if (amount > 0) {
+ /* Ok, some data to move. Just move it. */
+ user->u_want_read = 0;
+ return 1;
+ } else {
+ /*
+ * No data to transfer. Mark operation failed due to
+ * insufficient data to read.
+ */
+ user->u_want_read = 1;
+ return 0;
+ }
+ } else {
+ unsigned char *ptr;
+ size_t size;
+ ssize_t r;
+
+ /*
+ * Compute the segment for writing data to. And if we do get
+ * such segment, receive data to it. The size == 0 case should
+ * not happen because we checked that there's space in red
+ * out buffer above.
+ */
+ cbuffer_fill_w_segment(user->u_red_out_buf, &ptr, &size);
+ r = gnutls_record_recv(user->u_tls_session, ptr, size);
+ if (r > 0) {
+ /* Received TLS data. */
+ cbuffer_commit_w_segment(user->u_red_out_buf, r);
+ user->u_want_read = 0;
+ return 1;
+ } else if (r == 0) {
+ /* Received TLS EOF. */
+ user->u_black_in_d_eof = 1;
+ user->u_want_read = 0;
+ return 1;
+ } else if (is_fatal_tls_error((int)r)) {
+ /* Fatal TLS error. */
+ return handle_tls_failure_code(user, (int)r, 0);
+ } else if (r < 0) {
+ /* Temporary read failure. */
+ user->u_want_read = 1 + gnutls_record_get_direction(
+ user->u_tls_session);
+ return 0;
+ }
+ }
+ /* Can't really come here. */
+ return 0;
+}
+
+/* Copy or encrypt data from red input to black output. */
+static int red_to_black_handler(struct user *user)
+{
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate if TLS is present but not active. */
+ if (user->u_tls_session && !user->u_tls_active)
+ return 0;
+ /* Require data in red in buffer. */
+ if (!cbuffer_used(user->u_red_in_buf))
+ return 0;
+ /* Don't fail like last time. */
+ if (!gnutls_blacks_activate2(user, user->u_want_write))
+ return 0;
+
+ if (!user->u_tls_session) {
+ /*
+ * No-TLS case. Just find maximum amount of data to move and
+ * then move the data.
+ */
+ size_t amount;
+ amount = cbuffer_move_nolimit(user->u_black_out_buf,
+ user->u_red_in_buf);
+ if (amount > 0) {
+ /* Ok, some data to move. Just move it. */
+ user->u_want_write = 0;
+ return 1;
+ } else {
+ /*
+ * No data to transfer. Mark operation failed due to
+ * insufficient space to write.
+ */
+ user->u_want_write = 2;
+ return 0;
+ }
+ } else {
+ unsigned char *ptr;
+ size_t size;
+ ssize_t r = 0;
+
+ /*
+ * Compute the segment for reading data to. And if we do get
+ * such segment, send data from it. The size == 0 case should
+ * not happen because we checked that there's data in red
+ * in buffer above.
+ */
+ cbuffer_fill_r_segment(user->u_red_in_buf, &ptr, &size);
+ if (!user->u_want_write)
+ /* Last was success, just send new record. */
+ r = gnutls_record_send(user->u_tls_session, ptr,
+ size);
+ else
+ /* Last time it failed, try to resend the record. */
+ r = gnutls_record_send(user->u_tls_session, NULL, 0);
+ if (r > 0) {
+ /* Sent some TLS data. */
+ cbuffer_commit_r_segment(user->u_red_in_buf, r);
+ user->u_want_write = 0;
+ return 1;
+ } else if (is_fatal_tls_error((int)r)) {
+ /* Fatal TLS error. */
+ return handle_tls_failure_code(user, (int)r, 0);
+ } else if (r < 0) {
+ /* Temporary send failure. */
+ user->u_want_write = 1 + gnutls_record_get_direction(user->u_tls_session);
+ return 0;
+ }
+ }
+ /* Can't really come here. */
+ return 0;
+}
+
+static const char hexes[] = "0123456789abcdef";
+
+#define TMPBUFSIZE 256
+
+/* Copy or encrypt data from red error to black out. */
+static int rederr_to_black_handler(struct user *user)
+{
+ unsigned char hdrbuf[8];
+ unsigned char buffer[TMPBUFSIZE];
+ size_t bufusage = 0;
+ size_t pcktsize;
+ unsigned char *segstart;
+ size_t seglen;
+ size_t maxread;
+
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate if TLS is not yet ready. */
+ if (user->u_tls_session && !user->u_tls_active)
+ return 0;
+ /* Don't activate if there's no data in red error. */
+ if (!cbuffer_used(user->u_red_err_buf))
+ return 0;
+ /* Don't activate if red in hasn't been closed. */
+ if (user->u_red_in_fd >= 0)
+ return 0;
+ /* Don't activate if red error can have more data. */
+ if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf))
+ return 0;
+ /* Don't fail like last write. */
+ if (!gnutls_blacks_activate2(user, user->u_want_write))
+ return 0;
+
+ /* Safety hatch. Truncate error if too long. */
+ if (!cbuffer_free(user->u_red_err_buf) && user->u_red_err_fd >= 0) {
+ force_close(user->u_red_err_fd);
+ user->u_red_err_fd = -1;
+ }
+
+ /* Fill the header. */
+ pcktsize = 8 + cbuffer_used(user->u_red_err_buf);
+ hdrbuf[0] = hexes[pcktsize / 4096 % 16];
+ hdrbuf[1] = hexes[pcktsize / 256 % 16];
+ hdrbuf[2] = hexes[pcktsize / 16 % 16];
+ hdrbuf[3] = hexes[pcktsize % 16];
+ hdrbuf[4] = 'E';
+ hdrbuf[5] = 'R';
+ hdrbuf[6] = 'R';
+ hdrbuf[7] = ' ';
+ /*
+ * If header hasn't been sent yet, copy the remainder of header to
+ * transfer buffer.
+ */
+ if (user->u_red_err_hdr_sent < 8) {
+ memcpy(buffer, hdrbuf + user->u_red_err_hdr_sent,
+ 8 - user->u_red_err_hdr_sent);
+ bufusage = (8 - user->u_red_err_hdr_sent);
+ }
+ maxread = TMPBUFSIZE - bufusage;
+
+ /*
+ * Request read segment out of red error. There's always data in
+ * red error buffer by checks above. Then copy as much data from
+ * it as possible.
+ */
+ cbuffer_fill_r_segment(user->u_red_err_buf, &segstart, &seglen);
+ if (maxread >= seglen) {
+ memcpy(buffer + bufusage, segstart, seglen);
+ bufusage += seglen;
+ } else {
+ memcpy(buffer + bufusage, segstart, maxread);
+ bufusage += maxread;
+ }
+
+ /*
+ * Bufusage is always positive, since its either equal to seglen
+ * or maxread, and neither can be zero.
+ */
+ if (!user->u_tls_session && !user->u_red_in_have_data) {
+ /*
+ * Compute maximum amount of data that can be copied to
+ * send buffer (no TLS case).
+ */
+ bufusage = cbuffer_write_max(user->u_black_out_buf, buffer,
+ bufusage);
+ if (bufusage > 0)
+ user->u_want_write = 0;
+ else {
+ /* No space, mark it failed due to write. */
+ user->u_want_write = 2;
+ return 0;
+ }
+ } else if (!user->u_red_in_have_data) {
+ ssize_t r;
+
+ if (!user->u_want_write)
+ /* Last was success, just send new record. */
+ r = gnutls_record_send(user->u_tls_session, buffer,
+ bufusage);
+ else
+ /* Last time it failed, try to resend the record. */
+ r = gnutls_record_send(user->u_tls_session, NULL, 0);
+ if (r > 0) {
+ /* Successfully sent data. Adjust bufusage. */
+ bufusage = r;
+ user->u_want_write = 0;
+ } else if (is_fatal_tls_error((int)r)) {
+ /* Fatal TLS error. */
+ return handle_tls_failure_code(user, (int)r, 0);
+ } else if (r < 0) {
+ /* Temporary failure. Mark the failure. */
+ user->u_want_write = 1 + gnutls_record_get_direction(
+ user->u_tls_session);
+ bufusage = 0;
+ return 0;
+ }
+ }
+
+ /* Now bufusage is set to amount of bytes sent. ACK the data sent. */
+ if ((size_t)user->u_red_err_hdr_sent + bufusage < 8) {
+ /* Partially sent header. ACK the header sent. */
+ user->u_red_err_hdr_sent += bufusage;
+ bufusage = 0;
+ return 1;
+ } else {
+ /*
+ * Completely sent the header. ACK rest of it and substract
+ * it from actual data sent.
+ */
+ bufusage -= (8 - user->u_red_err_hdr_sent);
+ user->u_red_err_hdr_sent = 8;
+ }
+ /* ACK the actual error data sent. */
+ if (bufusage > 0)
+ cbuffer_commit_r_segment(user->u_red_err_buf, bufusage);
+
+ return 1;
+}
+
+/* Try to send delayed alert. */
+static int tls_alert_handler(struct user *user)
+{
+ int r;
+
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't do this without delayed tls failure. */
+ if (!user->u_delay_tls_failure)
+ return 0;
+ /* Don't fail like last time. */
+ if (!gnutls_blacks_activate2(user, user->u_want_hand))
+ return 0;
+
+ r = gnutls_alert_send(user->u_tls_session, GNUTLS_AL_FATAL,
+ user->u_delay_alert);
+ if (r == 0) {
+ /* The user->u_delay_tls_failure may be 2 too. */
+ if (user->u_delay_tls_failure == 1)
+ delay_cleanup_user(user, USER_TLS_HAND_ERROR,
+ "Fatal alert sent");
+ else
+ delay_cleanup_user(user, USER_TLS_ERROR,
+ "Fatal alert sent");
+ return 0;
+ } else if (is_fatal_tls_error(r)) {
+ const char *err = gnutls_strerror_name((int)r);
+ delay_cleanup_user(user, USER_TLS_ERROR, err);
+ return 1;
+ } else if (r < 0) {
+ /* Still needs more attempts to send. */
+ user->u_want_hand = 1 + gnutls_record_get_direction(
+ user->u_tls_session);
+ return 0;
+ }
+ /* Can't really come here. */
+ return 0;
+}
+
+/* Try to handshake TLS connection. */
+static int handshake_handler(struct user *user)
+{
+ int r;
+
+ /* Don't activate on already failed connections. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Don't activate if TLS is ready or no TLS. */
+ if (user->u_tls_active || !user->u_tls_session)
+ return 0;
+ /* Don't handshake anoymore with delayed TLS failure. */
+ if (user->u_delay_tls_failure)
+ return 0;
+ /* Don't fail like last time. */
+ if (!gnutls_blacks_activate2(user, user->u_want_hand))
+ return 0;
+
+ r = gnutls_handshake(user->u_tls_session);
+ if (r == 0) {
+ /* Handshake completed, TLS ready. */
+ user->u_want_hand = 0;
+ user->u_tls_active = 1;
+ return 1;
+ } else if (is_fatal_tls_error(r)) {
+ /* Fatal TLS error. */
+ return handle_tls_failure_code(user, (int)r, 1);
+ } else if (r < 0) {
+ /* Still needs more handshaking. */
+ user->u_want_hand = 1 + gnutls_record_get_direction(
+ user->u_tls_session);
+ return 0;
+ }
+ /* Can't really come here. */
+ return 0;
+}
+
+/*
+ * Compare two timevals. Returns -1 if first is first, 0 if same, 1 otherwise
+ */
+static int tv_compare(const struct timeval *tv1, const struct timeval *tv2)
+{
+ if (tv1->tv_sec == -1)
+ return (tv2->tv_sec == -1) ? 0 : 1;
+ if (tv2->tv_sec == -1)
+ return -1;
+ if (tv1->tv_sec > tv2->tv_sec)
+ return 1;
+ if (tv1->tv_sec < tv2->tv_sec)
+ return -1;
+ if (tv1->tv_usec > tv2->tv_usec)
+ return 1;
+ if (tv1->tv_usec < tv2->tv_usec)
+ return -1;
+ return 0;
+}
+
+/* Handle timeouts on connection. */
+static int timeout_handler(struct user *user)
+{
+ struct timeval t;
+
+ gettimeofday(&t, NULL);
+
+ /* Don't activate on already failed connection. */
+ if (user->u_failure || user->u_delay_failure)
+ return 0;
+ /* Is it time to activate? */
+ if (tv_compare(&user->u_deadline, &t) >= 0)
+ return 0;
+
+ cleanup_user(user, USER_TIMEOUT, "Request timeout");
+ return 0;
+}
+
+/* GnuTLS push function. */
+static ssize_t gnutls_push_data(gnutls_transport_ptr_t ptr, const void *data,
+ size_t size)
+{
+ struct user *user = (struct user*)ptr;
+
+ /* Compute maximum transfer. */
+ if (size > cbuffer_free(user->u_black_out_buf))
+ size = cbuffer_free(user->u_black_out_buf);
+
+ /* If no data can be read, send EAGAIN. */
+ if (size == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ cbuffer_write(user->u_black_out_buf, (unsigned char*)data, size);
+ return size;
+}
+
+/* GnuTLS pull function. */
+static ssize_t gnutls_pull_data(gnutls_transport_ptr_t ptr, void *data,
+ size_t size)
+{
+ struct user *user = (struct user*)ptr;
+
+ /* Compute maximum transfer. */
+ if (size > cbuffer_used(user->u_black_in_buf))
+ size = cbuffer_used(user->u_black_in_buf);
+
+ /* If no data can be written, send EAGAIN. */
+ if (size == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ cbuffer_read(user->u_black_in_buf, (unsigned char*)data, size);
+ return size;
+
+}
+
+void user_configure_tls(struct user *user, gnutls_session_t session)
+{
+ user->u_tls_session = session;
+ user->u_want_hand = 0;
+ user->u_tls_active = 0;
+ /* Configure the TLS session transport level. */
+ gnutls_transport_set_ptr(session, user);
+ gnutls_transport_set_push_function(session, gnutls_push_data);
+ gnutls_transport_set_pull_function(session, gnutls_pull_data);
+ gnutls_transport_set_lowat(session, 0);
+ /*
+ * For security reasons, clear the red output as that can no
+ * longer be trusted.
+ */
+ cbuffer_clear(user->u_red_out_buf);
+}
+
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+ fd_set *wfds, struct timeval *deadline)
+{
+ /*
+ * Execute all service handlers in case they alter what
+ * files should be waited on.
+ */
+ user_service_nofd(user);
+
+ /* Adjust deadline. */
+ if (tv_compare(deadline, &user->u_deadline) > 0)
+ *deadline = user->u_deadline;
+
+ if (user->u_red_out_fd >= 0 && cbuffer_used(user->u_red_out_buf)) {
+ /* Red out is writable. */
+ FD_SET(user->u_red_out_fd, wfds);
+ if (*bound <= user->u_red_out_fd)
+ *bound = user->u_red_out_fd + 1;
+ }
+ if (user->u_black_fd >= 0 && !user->u_black_in_eof &&
+ /* Black in is readable. */
+ cbuffer_free(user->u_black_in_buf)) {
+ FD_SET(user->u_black_fd, rfds);
+ if (*bound <= user->u_black_fd)
+ *bound = user->u_black_fd + 1;
+ }
+ /* Intentionally bias red to black towards writing to black. */
+ if (user->u_black_fd >= 0 && !user->u_black_out_eof &&
+ /* Black out is writable. */
+ cbuffer_used(user->u_black_out_buf)) {
+ FD_SET(user->u_black_fd, wfds);
+ if (*bound <= user->u_black_fd)
+ *bound = user->u_black_fd + 1;
+ } else if (user->u_red_in_fd >= 0 &&
+ cbuffer_free(user->u_red_in_buf)) {
+ /* Red in is readable. */
+ FD_SET(user->u_red_in_fd, rfds);
+ if (*bound <= user->u_red_in_fd)
+ *bound = user->u_red_in_fd + 1;
+ }
+ if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) {
+ /* Red err is readable. */
+ FD_SET(user->u_red_err_fd, rfds);
+ if (*bound <= user->u_red_err_fd)
+ *bound = user->u_red_err_fd + 1;
+ }
+}
+
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds)
+{
+ int newr = 1;
+
+ /* Do this until no service handler makes any progress. */
+ while (newr) {
+ newr = 0;
+ newr = newr | black_in_handler(user, rfds);
+ newr = newr | black_out_handler(user, wfds);
+ newr = newr | red_in_handler(user, rfds);
+ newr = newr | red_out_handler(user, wfds);
+ newr = newr | red_err_handler(user, rfds);
+ newr = newr | black_out_eof_handler(user);
+ newr = newr | red_out_eof_handler(user);
+ newr = newr | connection_eof_handler(user);
+ newr = newr | black_to_red_handler(user);
+ newr = newr | red_to_black_handler(user);
+ newr = newr | rederr_to_black_handler(user);
+ newr = newr | timeout_handler(user);
+ newr = newr | handshake_handler(user);
+ newr = newr | tls_alert_handler(user);
+ newr = newr | process_delay_failure(user);
+ }
+}
+
+void user_service_nofd(struct user *user)
+{
+ fd_set rfds;
+ fd_set wfds;
+
+ /* Clear the fd sets, we don't do I/O here. */
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ user_service(user, &rfds, &wfds);
+}
+
+gnutls_session_t user_get_tls(struct user *user)
+{
+ if (user->u_tls_session && user->u_tls_active)
+ return user->u_tls_session;
+ else
+ return NULL;
+}
+
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err)
+{
+ user->u_red_in_fd = red_in;
+ user->u_red_out_fd = red_out;
+ user->u_red_err_fd = red_err;
+ if (red_in >= 0)
+ fcntl(red_in, F_SETFL, fcntl(red_in, F_GETFL) | O_NONBLOCK);
+ if (red_out >= 0)
+ fcntl(red_out, F_SETFL, fcntl(red_out, F_GETFL) | O_NONBLOCK);
+ if (red_err >= 0)
+ fcntl(red_err, F_SETFL, fcntl(red_err, F_GETFL) | O_NONBLOCK);
+}
+
+void user_clear_red_io(struct user *user)
+{
+ if (user->u_red_out_fd >= 0)
+ force_close(user->u_red_out_fd);
+ user->u_red_out_fd = -1;
+}
+
+struct cbuffer *user_get_red_in(struct user *user)
+{
+ if (user->u_red_in_fd >= 0)
+ return NULL;
+ else
+ return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out(struct user *user)
+{
+ if (user->u_red_out_fd >= 0)
+ return NULL;
+ else
+ return user->u_red_out_buf;
+}
+
+int user_get_failure(struct user *user)
+{
+ return user->u_failure;
+}
+
+const char *user_get_error(struct user *user)
+{
+ return user->u_errmsg;
+}
+
+void user_send_red_in_eof(struct user *user)
+{
+ user->u_red_assume_more = 0;
+}
+
+struct cbuffer *user_get_red_err(struct user *user)
+{
+ if (user->u_red_err_fd >= 0 || user->u_red_in_have_data)
+ return NULL;
+ else
+ return user->u_red_err_buf;
+}
+
+void user_clear_deadline(struct user *user)
+{
+ user->u_deadline.tv_sec = -1;
+}
+
+size_t round_up(size_t base, size_t divide)
+{
+ return base + (divide - base % divide) % divide;
+}
+
+struct user *user_create(int black_fd, unsigned deadline_secs)
+{
+ struct timeval t;
+ size_t offset[5];
+#ifdef USE_TRAP_PAGING
+ size_t trap[6];
+#endif
+ int i;
+
+ for (i = 0; i < 5; i++)
+ offset[i] = i * BUFFERSIZE;
+
+ fcntl(black_fd, F_SETFL, fcntl(black_fd, F_GETFL) | O_NONBLOCK);
+
+ /* Compute the deadline field value. */
+ gettimeofday(&t, NULL);
+ t.tv_sec += deadline_secs;
+
+ /* How much to allocate for buffers? */
+ int r = 0;
+ size_t allocsize = 4 * BUFFERSIZE + ERR_BUFFERSIZE;
+
+ /* Allocate the primary structure. */
+ struct user *user = (struct user*)malloc(sizeof(struct user));
+ if (!user)
+ return NULL;
+
+ /* Allocate memory for buffer backing buffer. */
+#ifdef USE_TRAP_PAGING
+ allocsize = 4 * round_up(BUFFERSIZE, getpagesize()) +
+ round_up(ERR_BUFFERSIZE, getpagesize()) +
+ 6 * getpagesize();
+
+ for (i = 0; i < 5; i++) {
+ offset[i] = i * round_up(BUFFERSIZE, getpagesize()) + (i + 1) * getpagesize();
+ trap[i] = offset[i] - getpagesize();
+ }
+ trap[5] = allocsize - getpagesize();
+
+ user->u_buf_backing_size = allocsize;
+ user->u_buf_backing = (unsigned char*)mmap(NULL,
+ user->u_buf_backing_size,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (user->u_buf_backing == MAP_FAILED)
+ r = -1;
+ for (i = 0; i < 6; i++)
+ if (mprotect(user->u_buf_backing + trap[i], getpagesize(),
+ PROT_NONE) < 0) {
+ munmap(user->u_buf_backing, user->u_buf_backing_size);
+ r = -1;
+ break;
+ }
+#else
+ user->u_buf_backing = (unsigned char*)malloc(allocsize);
+ user->u_buf_backing_size = 0;
+ if (!user->u_buf_backing)
+ r = -1;
+#endif
+ if (r < 0) {
+ free(user);
+ return NULL;
+ }
+
+ user->u_black_in_eof = 0;
+ user->u_black_out_eof = 0;
+ user->u_black_in_d_eof = 0;
+ user->u_black_out_d_eof = 0;
+ user->u_want_read = 0;
+ user->u_want_write = 0;
+ user->u_want_hand = 0;
+ user->u_red_assume_more = 1;
+ user->u_red_in_have_data = 0;
+ user->u_red_err_hdr_sent = 0;
+ user->u_tls_active = 0;
+ user->u_black_fd = black_fd;
+ user->u_red_in_fd = -1;
+ user->u_red_out_fd = -1;
+ user->u_red_err_fd = -1;
+ user->u_black_in_buf = cbuffer_create(user->u_buf_backing +
+ offset[0], BUFFERSIZE);
+ user->u_black_out_buf = cbuffer_create(user->u_buf_backing +
+ offset[1], BUFFERSIZE);
+ user->u_red_in_buf = cbuffer_create(user->u_buf_backing +
+ offset[2], BUFFERSIZE);
+ user->u_red_out_buf = cbuffer_create(user->u_buf_backing +
+ offset[3], BUFFERSIZE);
+ user->u_red_err_buf = cbuffer_create(user->u_buf_backing +
+ offset[4], ERR_BUFFERSIZE);
+ if (!user->u_black_in_buf || !user->u_black_out_buf ||
+ !user->u_red_in_buf || !user->u_red_out_buf ||
+ !user->u_red_err_buf) {
+ /* Failed to allocate memory. */
+ cbuffer_destroy(user->u_black_in_buf);
+ cbuffer_destroy(user->u_black_out_buf);
+ cbuffer_destroy(user->u_red_in_buf);
+ cbuffer_destroy(user->u_red_out_buf);
+ cbuffer_destroy(user->u_red_err_buf);
+#ifdef USE_TRAP_PAGING
+ munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+ free(user->u_buf_backing);
+#endif
+ free(user);
+ return NULL;
+ }
+ user->u_deadline = t;
+ user->u_failure = USER_STILL_ACTIVE;
+ user->u_delay_failure = USER_STILL_ACTIVE;
+ user->u_errmsg = NULL;
+ user->u_tls_session = NULL;
+ user->u_delay_tls_failure = 0;
+ return user;
+}
+
+
+
+void user_release(struct user *user)
+{
+ if (!user->u_failure)
+ cleanup_user(user, USER_KILL, "User session killed");
+
+ cbuffer_destroy(user->u_black_in_buf);
+ cbuffer_destroy(user->u_black_out_buf);
+ cbuffer_destroy(user->u_red_in_buf);
+ cbuffer_destroy(user->u_red_out_buf);
+ cbuffer_destroy(user->u_red_err_buf);
+ free(user->u_errmsg);
+
+#ifdef USE_TRAP_PAGING
+ munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+ free(user->u_buf_backing);
+#endif
+ free(user);
+}
+
+int user_tls_configured(struct user *user)
+{
+ return (user->u_tls_session != NULL);
+}
+
+const char *user_explain_failure(int code)
+{
+ switch(code) {
+ case USER_STILL_ACTIVE:
+ return "Still active";
+ case USER_CONNECTION_END:
+ return "Connection closed";
+ case USER_LAYER4_ERROR:
+ return "Transport error";
+ case USER_TLS_ERROR:
+ return "TLS error";
+ case USER_TLS_HAND_ERROR:
+ return "TLS handshake error";
+ case USER_KILL:
+ return "User killed";
+ case USER_RED_FAILURE:
+ return "Subprocess failure";
+ case USER_TIMEOUT:
+ return "Timeout";
+ default:
+ return "Unknown error";
+ }
+}
+
+struct cbuffer *user_get_red_in_force(struct user *user)
+{
+ return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out_force(struct user *user)
+{
+ return user->u_red_out_buf;
+}
+
+struct cbuffer *user_get_red_err_force(struct user *user)
+{
+ return user->u_red_err_buf;
+}
+
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert)
+{
+ char error_buffer[8192];
+ if (!user->u_tls_session)
+ return;
+
+ user->u_delay_tls_failure = 1;
+ user->u_delay_alert = alert;
+ sprintf(error_buffer, "TLS alert forced: %s",
+ gnutls_alert_get_name(alert));
+ user->u_errmsg = strdup(error_buffer);
+}
+
+int user_red_out_eofd(struct user *user)
+{
+ if (user->u_red_out_fd >= 0)
+ return 0;
+ if (user->u_tls_session && !user->u_black_in_d_eof)
+ return 0;
+ if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+ return 0;
+ if (!user->u_tls_session && !user->u_black_in_eof)
+ return 0;
+ return 1;
+}
diff --git a/git-over-tls/user.h b/git-over-tls/user.h
new file mode 100644
index 0000000..e921c98
--- /dev/null
+++ b/git-over-tls/user.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _user__h__included__
+#define _user__h__included__
+
+#include <gnutls/gnutls.h>
+#include "cbuffer.h"
+#include <stdint.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminoloyy:
+ * black:
+ * Side of user session connected to socket. Transports
+ * unencrypted or TLS data between peers.
+ * red:
+ * Side of user session connected to server loop or to
+ * helper program.
+ * red input:
+ * Input buffer unencrypted data is read from. May be connected
+ * to file descriptor. In that case that file descriptor is
+ * read for data.
+ * red output:
+ * Output buffer decrypted data is written to. May be connected
+ * to file descriptor. In that cse that file descriptor is
+ * written with the data.
+ * red error:
+ * Input buffer error input is read from. May be connecte to
+ * file descriptor, which is read for input data. If red input
+ * file descriptor has data succesfully read, the red error buffer
+ * is cleared and any further error input is redirected to bit
+ * bucket. If red input closes with red error having data, then
+ * that data is sent as ERR packet when red error has associated
+ * file descriptor closed.
+ * red_in:
+ * File descriptor associated with red input.
+ * red_out:
+ * File descriptor associated with red output.
+ * red_err:
+ * File descriptor associated with red error.
+ * deadline:
+ * Time to disconnect (some) client on or perform some other
+ * service.
+ */
+
+/* Main session structure. Opaque type. */
+struct user;
+
+/* Failure codes. */
+/* User still active. */
+#define USER_STILL_ACTIVE 0
+/* Connection normal end. */
+#define USER_CONNECTION_END 1
+/* Transport error. */
+#define USER_LAYER4_ERROR -1
+/* TLS error while not handshaking. */
+#define USER_TLS_ERROR -2
+/* TLS handshake error. */
+#define USER_TLS_HAND_ERROR -3
+/* User killed. Never returned as failure code. */
+#define USER_KILL -4
+/* Red file descriptor I/O operation failure. */
+#define USER_RED_FAILURE -5
+/* User timed out. */
+#define USER_TIMEOUT -6
+
+/*
+ * Create new user session.
+ *
+ * Input:
+ * black_fd Black fd. Must be socket.
+ * timeout_secs Initial timeout in seconds.
+ *
+ * Output:
+ * Return value Newly created session, or NULL on out of
+ * memory.
+ */
+struct user *user_create(int black_fd, unsigned timeout_secs);
+
+/*
+ * Configure session to use TLS. The TLS session must be preconfigured
+ * (credentials set, etc), but not handshaked.
+ *
+ * Input:
+ * user The user session to manipulate.
+ * session TLS session to assign.
+ */
+void user_configure_tls(struct user *user, gnutls_session_t session);
+
+/*
+ * Add current user session file descriptors that are ready to read or
+ * write to file descriptor sets for read or write. Also update dead-
+ * line if needed.
+ *
+ * Input:
+ * user The user session to handle.
+ * bound Current file descriptor bound. 0 if there are
+ * no file descriptors in either set.
+ * rfds Read file descriptor set.
+ * wfds Write file descriptor set.
+ * deadline Current deadline for select.
+ *
+ * Output:
+ * bound Updated file descriptor bound.
+ * rfds Updated read file descriptor set.
+ * wfds Updated write file descriptor set.
+ * deadline Updated deadline for select.
+ */
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+ fd_set *wfds, struct timeval *deadline);
+
+/*
+ * Service this user.
+ *
+ * Input:
+ * user The user session to handle.
+ * rfds Ready to read file descriptors.
+ * wfds Ready to write file descriptors.
+ */
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds);
+
+/*
+ * Service this user without doing any I/O
+ *
+ * Input:
+ * user The user session to handle.
+ */
+void user_service_nofd(struct user *user);
+
+/*
+ * Get failure class.
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value 0 if connection is active, 1 if end
+ * of connection, negative code if connection
+ * ended with error. See USER_* defintions.
+ */
+int user_get_failure(struct user *user);
+
+/*
+ * Get explanation of failure code.
+ *
+ * Input:
+ * code The failure code.
+ *
+ * Output:
+ * Return value String explaining the code. Do not
+ * free this.
+ */
+const char *user_explain_failure(int code);
+
+/*
+ * Get more detailed error message to explain the failure.
+ *
+ * Input:
+ * user The user session.
+ *
+ * Output:
+ * Return value Error message or NULL if there is
+ * no more detailed error message. Do
+ * not free this.
+ *
+ * Notes:
+ * - Error message can be NULL or not independently of
+ * main failure status.
+ */
+const char *user_get_error(struct user *user);
+
+/*
+ * Has TLS been configured?
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value Nonzero if configured, zero if not.
+ */
+/* Returns 1 if TLS has been configured, 0 otherwise. */
+int user_tls_configured(struct user *user);
+
+/*
+ * Return TLS session associated with user session.
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value If TLS is configured and has handshaked, the TLS
+ * session. Otherwise NULL.
+ */
+gnutls_session_t user_get_tls(struct user *user);
+
+/*
+ * Free user structure. If user is still active, disconnect user hard.
+ *
+ * Input:
+ * user The user session to release.
+ */
+void user_release(struct user *user);
+
+/*
+ * Set red I/O file descriptors.
+ *
+ * Input:
+ * user The user session to manipulate.
+ * red_in Red input file descriptor, -1 for none.
+ * red_out Red output file descritpor, -1 for none.
+ * red_err Red error file descriptor, -1 for none.
+ *
+ * Notes:
+ * - red_in and red_err must be read ends if present.
+ * - red_out must be write end if present.
+ */
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err);
+
+/*
+ * Clear red I/O by closing red output and marking no red output file
+ * descriptor. This may be neeeded, since red out can't close without
+ * input to transfer.
+ *
+ * Input:
+ * user The user session to manipulate.
+ */
+void user_clear_red_io(struct user *user);
+
+/*
+ * Get red input buffer.
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value If red_in is set to none, the buffer, otherwise
+ * NULL.
+ */
+struct cbuffer *user_get_red_in(struct user *user);
+
+/*
+ * Get red output buffer.
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value If red_out is set to none, the buffer, otherwise
+ * NULL.
+ */
+struct cbuffer *user_get_red_out(struct user *user);
+
+/*
+ * Get red error buffer.
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value If red_err is set to none and no data has been
+ * received through red_in, the buffer, otherwise
+ * NULL.
+ */
+struct cbuffer *user_get_red_err(struct user *user);
+
+/*
+ * Clear deadline (don't generate timeout anymore).
+ *
+ * Input:
+ * user The user session to manipulate.
+ */
+void user_clear_deadline(struct user *user);
+
+/*
+ * Send EOF to red input.
+ *
+ * Input:
+ * user The user session to manipulate.
+ *
+ * Notes:
+ * - Only works if red input has no file descriptor associated.
+ * - EOF is automatically sent if red_in encounters EOF.
+ */
+void user_send_red_in_eof(struct user *user);
+
+/*
+ * Force get red input buffer (even if it shouldn't be available).
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value Red input buffer.
+ */
+struct cbuffer *user_get_red_in_force(struct user *user);
+
+/*
+ * Force get red output buffer (even if it shouldn't be available).
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value Red output buffer.
+ */
+struct cbuffer *user_get_red_out_force(struct user *user);
+
+/*
+ * Force get red error buffer (even if it shouldn't be available).
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value Red error buffer.
+ */
+struct cbuffer *user_get_red_err_force(struct user *user);
+
+/*
+ * Send fatal TLS alert.
+ *
+ * Input:
+ * user The user session to manipulate.
+ * alert The alert to send.
+ *
+ * Notes:
+ * - Ignored if no TLS has been configured.
+ */
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert);
+
+/*
+ * Has red output been EOF'd?
+ *
+ * Input:
+ * user The user session to interrogate.
+ *
+ * Output:
+ * Return value Nonzero if red out can receive no more data and
+ * not connected to file descriptor, otherwise zero
+ * (more data possible).
+ */
+int user_red_out_eofd(struct user *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--
1.6.6.102.gd6f8f.dirty
^ permalink raw reply related [flat|nested] 28+ messages in thread