* [RFC 0/2] Git-over-TLS (gits://) client side support
@ 2010-01-13 13:19 Ilari Liusvaara
2010-01-13 13:19 ` [RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2) Ilari Liusvaara
` (3 more replies)
0 siblings, 4 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
To: git
This is client-side support for Git-over-TLS (gits://). gits:// is
version of git:// protocol layered on top of TLS (Transport Layer
Security). If using TLS, it is autenticated transport supporing
fetching, pushing and remote archive (plus special commands that
have server-dependent meaning).
Needs GnuTLS, and adds new make option NO_GNUTLS that disables builing
this code.
Supported underlying stream transports include TCP/IP, TCP/IPv6 and
Unix domain sockets (including Linux abstract namespace).
Supported authentication mechanisms include passwords, keypairs and on
some platforms Unix authentication if using unix domain sockets. Server
is authenticated using keypair (hostkey).
The patch is split into two parts because it would be otherwise be
too large for this list. Included are all the needed client side
utilities (some of them run gpg internally).
The main repo for gits:// implementation is
git://repo.or.cz/git-daemon2.git , which includes selfstanding client
code and server code.
Ilari Liusvaara (2):
Git-over-TLS (gits://) client side support (part 1 of 2)
Git-over-TLS (gits://) client side support (part 2 of 2)
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 +
git-over-tls/main.c | 460 +++++++++++
git-over-tls/misc.c | 15 +
git-over-tls/misc.h | 27 +
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/srp_askpass.h | 14 +
git-over-tls/user.c | 1384 ++++++++++++++++++++++++++++++++
git-over-tls/user.h | 357 ++++++++
30 files changed, 5585 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
create mode 100644 git-over-tls/main.c
create mode 100644 git-over-tls/misc.c
create mode 100644 git-over-tls/misc.h
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
create mode 100644 git-over-tls/srp_askpass.h
create mode 100644 git-over-tls/user.c
create mode 100644 git-over-tls/user.h
^ permalink raw reply [flat|nested] 28+ messages in thread
* [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
* Re: [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of 2)
2010-01-13 13:19 ` [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 " Ilari Liusvaara
@ 2010-01-13 13:25 ` Alex Riesen
0 siblings, 0 replies; 28+ messages in thread
From: Alex Riesen @ 2010-01-13 13:25 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: git
On Wed, Jan 13, 2010 at 14:19, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> +char *copy_alloc(const char *str, size_t len)
> +{
> + char *copy;
> +
> + copy = xmalloc(len + 1);
> + copy[len] = 0;
> + strncpy(copy, str, len);
> + return copy;
> +}
Some know this code as strndup(3):
http://linux.die.net/man/3/strndup
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
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 ` [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 " Ilari Liusvaara
@ 2010-01-13 13:39 ` Nguyen Thai Ngoc Duy
2010-01-13 13:57 ` Ilari Liusvaara
2010-01-13 20:13 ` Edward Z. Yang
3 siblings, 1 reply; 28+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2010-01-13 13:39 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: git
On 1/13/10, Ilari Liusvaara <ilari.liusvaara@elisanet.fi> wrote:
> This is client-side support for Git-over-TLS (gits://). gits:// is
> version of git:// protocol layered on top of TLS (Transport Layer
> Security). If using TLS, it is autenticated transport supporing
> fetching, pushing and remote archive (plus special commands that
> have server-dependent meaning).
>
> Needs GnuTLS, and adds new make option NO_GNUTLS that disables builing
> this code.
>
> Supported underlying stream transports include TCP/IP, TCP/IPv6 and
> Unix domain sockets (including Linux abstract namespace).
>
> Supported authentication mechanisms include passwords, keypairs and on
> some platforms Unix authentication if using unix domain sockets. Server
> is authenticated using keypair (hostkey).
>
> The patch is split into two parts because it would be otherwise be
> too large for this list. Included are all the needed client side
> utilities (some of them run gpg internally).
Can we rely on an external program, like stunnel, to do the job instead?
--
Duy
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 13:39 ` [RFC 0/2] Git-over-TLS (gits://) client side support Nguyen Thai Ngoc Duy
@ 2010-01-13 13:57 ` Ilari Liusvaara
2010-01-13 14:12 ` Andreas Krey
2010-01-13 19:11 ` Avery Pennarun
0 siblings, 2 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 13:57 UTC (permalink / raw)
To: Nguyen Thai Ngoc Duy; +Cc: git
On Wed, Jan 13, 2010 at 08:39:12PM +0700, Nguyen Thai Ngoc Duy wrote:
>
> Can we rely on an external program, like stunnel, to do the job instead?
No. The way authentication is done is very unusual. I don't think stunnel (or
anything else) can deal with such modes. And the reason authentications are
done like they are done in order to minimize points of failure (getting
really annoyed at failure modes sshd introduced was one big reason for
writing this).
I _definitely_ do not want to mess with X.509. And its not just about me
messing with it, it is also about pushing it to users.
And one would need custom daemon anyway even if one used stunnel.
git-daemon just can't deal with authentication data.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 13:57 ` Ilari Liusvaara
@ 2010-01-13 14:12 ` Andreas Krey
2010-01-13 14:47 ` Ilari Liusvaara
2010-01-13 19:11 ` Avery Pennarun
1 sibling, 1 reply; 28+ messages in thread
From: Andreas Krey @ 2010-01-13 14:12 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:
...
> And one would need custom daemon anyway even if one used stunnel.
> git-daemon just can't deal with authentication data.
It doesn't need to, really. stunnel sets the environment variable
SSL_CLIENT_DN with the distinguished name of the client certificate,
which can be used in the hook scripts ('update') on the server.
(I looked into that stuff once, but with the advent of smart-http(s)
I pretty much lost any interest to try implementing gits:// via
openssl here, as it isn't yet an actual itch.)
Andreas
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 14:12 ` Andreas Krey
@ 2010-01-13 14:47 ` Ilari Liusvaara
2010-01-13 16:17 ` Andreas Krey
0 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 14:47 UTC (permalink / raw)
To: Andreas Krey; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 03:12:18PM +0100, Andreas Krey wrote:
> On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:
> ...
> > And one would need custom daemon anyway even if one used stunnel.
> > git-daemon just can't deal with authentication data.
>
> It doesn't need to, really. stunnel sets the environment variable
> SSL_CLIENT_DN with the distinguished name of the client certificate,
> which can be used in the hook scripts ('update') on the server.
That would be useless. Data about authenticated client needs to fed to
authorization decisions already before invoking git.
And besides: Gits:// uses certificates as keypairs, which would make DN
data absolutely useless because it is untrustworthy. And adding PKI
is way too complicated.
> (I looked into that stuff once, but with the advent of smart-http(s)
> I pretty much lost any interest to try implementing gits:// via
> openssl here, as it isn't yet an actual itch.)
The authentication support for smart-http seems pretty bad (making the
old mistake of not binding authentications). Of course, the same tricks
as gits:// uses would work with https:// (its all TLS-level stuff), but
no server or client does that.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 14:47 ` Ilari Liusvaara
@ 2010-01-13 16:17 ` Andreas Krey
2010-01-13 17:36 ` Ilari Liusvaara
0 siblings, 1 reply; 28+ messages in thread
From: Andreas Krey @ 2010-01-13 16:17 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, 13 Jan 2010 16:47:47 +0000, Ilari Liusvaara wrote:
...
> > It doesn't need to, really. stunnel sets the environment variable
> > SSL_CLIENT_DN with the distinguished name of the client certificate,
> > which can be used in the hook scripts ('update') on the server.
>
> That would be useless. Data about authenticated client needs to fed to
> authorization decisions already before invoking git.
If you don't want public read access then you need to wedge a script
between stunnel and git itself that checks whether authentication is
present, yes.
> And besides: Gits:// uses certificates as keypairs,
My gripe with this is that I would expect gits: to be the same
as git: except that there is SSL underneath. git: does not have
authentication, so there should be none in gits: except what
SSL provides. (And the auth via unix domain sockets would be
usable for plain git: as well; there is no reason to encrypt
local traffic?)
(Is the unix auth via unix domain sockets part of GnuTLS?)
> which would make DN
> data absolutely useless because it is untrustworthy. And adding PKI
> is way too complicated.
That's another story. I think that it would be possible nowadays
to implement gits:// (in both ways) via core.gitproxy and a server-side
wrapper program (stunnel or else), but that has the disadvantage of
being unable to just provide a clone url without installing special
software besides git.
...
> The authentication support for smart-http seems pretty bad (making the
> old mistake of not binding authentications).
Mind to explain 'binding authentications'?
Andreas
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 16:17 ` Andreas Krey
@ 2010-01-13 17:36 ` Ilari Liusvaara
2010-01-13 18:35 ` Andreas Krey
0 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 17:36 UTC (permalink / raw)
To: Andreas Krey; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 05:17:11PM +0100, Andreas Krey wrote:
> On Wed, 13 Jan 2010 16:47:47 +0000, Ilari Liusvaara wrote:
>
> If you don't want public read access then you need to wedge a script
> between stunnel and git itself that checks whether authentication is
> present, yes.
That would violate layering badly. You need to decode the request
first before you can authorize. And the git daemon does that.
> > And besides: Gits:// uses certificates as keypairs,
>
> My gripe with this is that I would expect gits: to be the same
> as git: except that there is SSL underneath. git: does not have
> authentication, so there should be none in gits: except what
> SSL provides.
All authentication in gits:// is at TLS (SSL) level or even lower.
> (And the auth via unix domain sockets would be
> usable for plain git: as well; there is no reason to encrypt
> local traffic?)
In fact, git-remote-gits has unencrypted mode (should be compatible
with git-daemon). The reason its there is mainly for Unix domain
sockets support and more advanced IPv6 support (interface indexes and
numeric addresses actually work).
> (Is the unix auth via unix domain sockets part of GnuTLS?)
No, that server-only feature is part of the OS itself. In fact, it
needs no client-side support.
> That's another story. I think that it would be possible nowadays
> to implement gits:// (in both ways) via core.gitproxy and a server-side
> wrapper program (stunnel or else), but that has the disadvantage of
> being unable to just provide a clone url without installing special
> software besides git.
GIT_PROXY abuse? There are even better ways: smart transport remote
helpers (in next I think). Git can actually dispatch those (and yes,
that's exactly what this uses).
And gits:// client is also buildable selfstanding. That would require
new client software, but its still nicer than GIT_PROXY abuse.
Another problem with GIT_PROXY abuse: How to deal with potentially
multiple custom protocols. Remote helpers can deal with that nicely.
> ...
> > The authentication support for smart-http seems pretty bad (making the
> > old mistake of not binding authentications).
>
> Mind to explain 'binding authentications'?
Actually, that was little badly choosen term and not the true problem,
but the basic problem is that one peer has to trust the the other peer's
authentication for security of its own authentication.
In how authentications used by gits:// are designed, even if client doesn't
detect trying to authenticate with attacker, the attacker doesn't get any
replayable credentials without breaking crypto keys (as opposed to just
passwords). This holds true even for password authentication (PAKE-type
scheme is used).
HTTP basic auth can be trivially sniffed if attacker can become other end
of the encrypted link (crypto is by far the strongest link...). Digest auth
is harder, but its essentially brute force against password (as opposed to
trying to break a key).
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 17:36 ` Ilari Liusvaara
@ 2010-01-13 18:35 ` Andreas Krey
2010-01-13 19:18 ` Ilari Liusvaara
0 siblings, 1 reply; 28+ messages in thread
From: Andreas Krey @ 2010-01-13 18:35 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, 13 Jan 2010 19:36:10 +0000, Ilari Liusvaara wrote:
...
> That would violate layering badly. You need to decode the request
> first before you can authorize. And the git daemon does that.
Well, yes. The script hackery would just decide between 'is allowed
to read (or write commits)' and 'is allowed to modify refs'. On the
other hand, git-daemon does not do fine-grained (read: per-branch)
access control, you'd only prevent pushing commits at all.
...
> > (Is the unix auth via unix domain sockets part of GnuTLS?)
>
> No, that server-only feature is part of the OS itself. In fact, it
> needs no client-side support.
Ok, then I'll be really interested in the server-side support and
the man pages on the whole stuff. Especially in how this is going
to be different from what ssh:// does or can do.
...
> GIT_PROXY abuse? There are even better ways: smart transport remote
> helpers (in next I think). Git can actually dispatch those (and yes,
> that's exactly what this uses).
Yeah, since the last mail I noticed that gitproxy is not quite what
some google hits suggest, and should have read the patch in some
more detail to find that gits is a remote helper.
Please consider my objections revoked, other than the claim that
it could be done with stunnel, however ugly that would be.
...
> Actually, that was little badly choosen term and not the true problem,
> but the basic problem is that one peer has to trust the the other peer's
> authentication for security of its own authentication.
I don't see how that would endanger the standard certificate auth in ssl
(client or server).
...
> HTTP basic auth can be trivially sniffed if attacker can become other end
> of the encrypted link
Of course, you have another problem in that case...also I'd personally
like to rely on ssl client certificates when using https.
Andreas
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 13:57 ` Ilari Liusvaara
2010-01-13 14:12 ` Andreas Krey
@ 2010-01-13 19:11 ` Avery Pennarun
2010-01-13 20:00 ` Ilari Liusvaara
1 sibling, 1 reply; 28+ messages in thread
From: Avery Pennarun @ 2010-01-13 19:11 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 8:57 AM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> On Wed, Jan 13, 2010 at 08:39:12PM +0700, Nguyen Thai Ngoc Duy wrote:
>>
>> Can we rely on an external program, like stunnel, to do the job instead?
>
> No. The way authentication is done is very unusual. I don't think stunnel (or
> anything else) can deal with such modes. And the reason authentications are
> done like they are done in order to minimize points of failure (getting
> really annoyed at failure modes sshd introduced was one big reason for
> writing this).
>
> I _definitely_ do not want to mess with X.509. And its not just about me
> messing with it, it is also about pushing it to users.
>
> And one would need custom daemon anyway even if one used stunnel.
> git-daemon just can't deal with authentication data.
It sounds to me like you're doing two different things with this patch series:
1) Adding additional authorization features (assuming the user is
already authenticated) to git-daemon
2) Creating a TLS encryption layer with authentication support.
#1 sounds like it could be its own patch series even if you don't have
#2, and could be reviewed separately.
#2 sounds like it is not even git-specific. You've decided that ssh
and stunnel don't fit your needs; what makes your solution not a
general TLS-based authentication layer, like stunnel but with
different certificate management? If it's really a general layer,
maybe it should be distributed separately and git could be taught how
to use it *or* stunnel (or ssh, as it does now) for its transport
encryption/authentication.
Have fun,
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 18:35 ` Andreas Krey
@ 2010-01-13 19:18 ` Ilari Liusvaara
2010-01-13 19:30 ` Avery Pennarun
2010-01-13 19:40 ` Andreas Krey
0 siblings, 2 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 19:18 UTC (permalink / raw)
To: Andreas Krey; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 07:35:20PM +0100, Andreas Krey wrote:
> On Wed, 13 Jan 2010 19:36:10 +0000, Ilari Liusvaara wrote:
>
> Ok, then I'll be really interested in the server-side support and
> the man pages on the whole stuff. Especially in how this is going
> to be different from what ssh:// does or can do.
That feature is grossly underdocumented (and also nonportable). Unix(7)
should document it, except that it doesn't for me (it documents that
SO_PASSCRED takes a boolean, except that what the server implementation
passes is something completely different).
I found the intformation about how to forcibly get peer UID on Linux
from one secure programming HOWTO.
One other software that I know uses similar stuff is D-BUS. AFAIK, SSH
can't do it.
Essentially, it involves asking the kernel about UID the socket peer
runs as (with local sockets, kernel knows that information).
> Please consider my objections revoked, other than the claim that
> it could be done with stunnel, however ugly that would be.
Only if you don't care about complexity introducing PKI would bring
(yes, I read those manuals).
> I don't see how that would endanger the standard certificate auth in ssl
> (client or server).
It doesn't, but...
> Of course, you have another problem in that case...also I'd personally
> like to rely on ssl client certificates when using https.
And how many (relative) use client ceritificates with SSL? Keypairs with SSH?
Why you think this is?
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 19:18 ` Ilari Liusvaara
@ 2010-01-13 19:30 ` Avery Pennarun
2010-01-13 20:06 ` Ilari Liusvaara
2010-01-13 19:40 ` Andreas Krey
1 sibling, 1 reply; 28+ messages in thread
From: Avery Pennarun @ 2010-01-13 19:30 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 2:18 PM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
>> Please consider my objections revoked, other than the claim that
>> it could be done with stunnel, however ugly that would be.
>
> Only if you don't care about complexity introducing PKI would bring
> (yes, I read those manuals).
I think you're overstating the situation a bit here. You can use
X.509 certificates without setting up a full PKI. Basically, an X.509
cert is just a public key with some extra crud thrown into the data
file. You could validate it using a PKI, but you could also validate
it by checking the verbatim public key just like ssh does. It's not
elegant, but it works, and it's a worldwide standard.
(I don't know if stunnel does this type of validation... but *I've*
done this with the openssl libraries, so I know it can be done.)
>> Of course, you have another problem in that case...also I'd personally
>> like to rely on ssl client certificates when using https.
>
> And how many (relative) use client ceritificates with SSL? Keypairs with SSH?
> Why you think this is?
At least hundreds of thousands of people, including non-technical
people, use X.509 client certificates and SSL in various big
industries with high security requirements. That's why every major
web browser supports them. In contrast, ssh is only ever used by
techies, and there are fewer of those. Of course, as techies our
informal observations might lead us to believe otherwise.
Furthermore, how many people who really want ssh-style keypairs (and
thus refuse to use X.509 and PKI) can't just use ssh as their git
transport? I don't actually understand what the goal is here.
Have fun,
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 19:18 ` Ilari Liusvaara
2010-01-13 19:30 ` Avery Pennarun
@ 2010-01-13 19:40 ` Andreas Krey
2010-01-13 20:47 ` Ilari Liusvaara
1 sibling, 1 reply; 28+ messages in thread
From: Andreas Krey @ 2010-01-13 19:40 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, 13 Jan 2010 21:18:02 +0000, Ilari Liusvaara wrote:
...
> That feature is grossly underdocumented (and also nonportable). Unix(7)
> should document it, except that it doesn't for me (it documents that
> SO_PASSCRED takes a boolean, except that what the server implementation
> passes is something completely different).
Actually, I meant how you plan to map credentials (however obtained)
into allowed actions inside git-daemon (or the hooks).
...
> And how many (relative) use client ceritificates with SSL? Keypairs with SSH?
> Why you think this is?
Because ssh is much more popular than ssl client auth. Obtaining client
certificates isn't much more complicated than getting an ssh account,
once you have scripts for the stuff ready.
But I wonder: When you want keypair auth, why not just use ssh?
I didn't quite understand the use case yet, it seems. With ssh
I have all the infrastructure like ssh-agent in place already;
with gits: (any kind of) it will be asked for sooner or later.
Andreas
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 19:11 ` Avery Pennarun
@ 2010-01-13 20:00 ` Ilari Liusvaara
0 siblings, 0 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 20:00 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 02:11:14PM -0500, Avery Pennarun wrote:
> On Wed, Jan 13, 2010 at 8:57 AM, Ilari Liusvaara
> <ilari.liusvaara@elisanet.fi> wrote:
> It sounds to me like you're doing two different things with this patch series:
>
> 1) Adding additional authorization features (assuming the user is
> already authenticated) to git-daemon
>
> 2) Creating a TLS encryption layer with authentication support.
>
> #1 sounds like it could be its own patch series even if you don't have
> #2, and could be reviewed separately.
This series (really only one patch, only split because its large) only
contains client parts, not server ones (not seperately or via patching
git-daemon).
And besides the daemon for gits:// was written from libraries up.
> #2 sounds like it is not even git-specific. You've decided that ssh
> and stunnel don't fit your needs; what makes your solution not a
> general TLS-based authentication layer, like stunnel but with
> different certificate management?
Stunnel seems mainly "tunnel stuff using SSL/TLS" type thing and any
support for auth in it seems afterthought. At least that's what I got
from reading the manuals for it.
> If it's really a general layer,
> maybe it should be distributed separately and git could be taught how
> to use it *or* stunnel (or ssh, as it does now) for its transport
> encryption/authentication.
The way serverside works is quite different from git-daemon. On client
side there are also some virtually inavoidable bidirectional couplings
(breaks layering) between generic and git-specific parts.
Yes, the code is split into two layers, but both layers contain git-
specific details. And the lower layer is low-level transport control code,
that doesn't even know how to configure TLS connection (that is quite
high-level task).
And ssh:// is not git:// tunneled over SSH, the request passing is done
differently.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 19:30 ` Avery Pennarun
@ 2010-01-13 20:06 ` Ilari Liusvaara
2010-01-13 20:13 ` Avery Pennarun
0 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 20:06 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 02:30:20PM -0500, Avery Pennarun wrote:
> On Wed, Jan 13, 2010 at 2:18 PM, Ilari Liusvaara
> <ilari.liusvaara@elisanet.fi> wrote:
>
> I think you're overstating the situation a bit here. You can use
> X.509 certificates without setting up a full PKI. Basically, an X.509
> cert is just a public key with some extra crud thrown into the data
> file. You could validate it using a PKI, but you could also validate
> it by checking the verbatim public key just like ssh does. It's not
> elegant, but it works, and it's a worldwide standard.
Grossly overcomplicated standard... ASN.1? And there are other usable
standards that can be used with TLS.
> (I don't know if stunnel does this type of validation... but *I've*
> done this with the openssl libraries, so I know it can be done.)
AFAIK, it doesn't.
> > And how many (relative) use client ceritificates with SSL? Keypairs with SSH?
> > Why you think this is?
>
> At least hundreds of thousands of people, including non-technical
> people, use X.509 client certificates and SSL in various big
> industries with high security requirements.
That is: Epsilon.
> That's why every major web browser supports them.
Supports != is actually usable.
> In contrast, ssh is only ever used by
> techies, and there are fewer of those. Of course, as techies our
> informal observations might lead us to believe otherwise.
Most of those that use git are techies anyway.
> Furthermore, how many people who really want ssh-style keypairs (and
> thus refuse to use X.509 and PKI) can't just use ssh as their git
> transport? I don't actually understand what the goal is here.
As said, I got fed up with failure modes of SSH.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 13:19 [RFC 0/2] Git-over-TLS (gits://) client side support Ilari Liusvaara
` (2 preceding siblings ...)
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, 0 replies; 28+ messages in thread
From: Edward Z. Yang @ 2010-01-13 20:13 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: git
Excerpts from Ilari Liusvaara's message of Wed Jan 13 08:19:44 -0500 2010:
> Supported authentication mechanisms include passwords, keypairs and on
> some platforms Unix authentication if using unix domain sockets. Server
> is authenticated using keypair (hostkey).
As a Git user, I'd like to say: it's about damn time! :-)
Cheers,
Edward
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 20:06 ` Ilari Liusvaara
@ 2010-01-13 20:13 ` Avery Pennarun
2010-01-13 21:04 ` Ilari Liusvaara
0 siblings, 1 reply; 28+ messages in thread
From: Avery Pennarun @ 2010-01-13 20:13 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> On Wed, Jan 13, 2010 at 02:30:20PM -0500, Avery Pennarun wrote:
>> That's why every major web browser supports them. [X.509 client-side certificates]
>
> Supports != is actually usable.
Lots of people use it. That was my point. If it weren't important,
web browser makers wouldn't bother putting it in; God knows they leave
out a lot of other stuff that I'd like.
>> Furthermore, how many people who really want ssh-style keypairs (and
>> thus refuse to use X.509 and PKI) can't just use ssh as their git
>> transport? I don't actually understand what the goal is here.
>
> As said, I got fed up with failure modes of SSH.
I think this is the answer that needs clarification. What failure
modes are these? ssh doesn't seem to fail for me. And github.com
seems to be working rather well with a huge number of users and ssh
authentication.
If you're upset at the failure modes of ssh, is it possible to fix ssh
instead of introducing Yet Another Tunneling Protocol?
Have fun,
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 19:40 ` Andreas Krey
@ 2010-01-13 20:47 ` Ilari Liusvaara
0 siblings, 0 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 20:47 UTC (permalink / raw)
To: Andreas Krey; +Cc: Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 08:40:50PM +0100, Andreas Krey wrote:
> On Wed, 13 Jan 2010 21:18:02 +0000, Ilari Liusvaara wrote:
> ...
> > That feature is grossly underdocumented (and also nonportable). Unix(7)
> > should document it, except that it doesn't for me (it documents that
> > SO_PASSCRED takes a boolean, except that what the server implementation
> > passes is something completely different).
>
> Actually, I meant how you plan to map credentials (however obtained)
> into allowed actions inside git-daemon (or the hooks).
Its actually git-daemon2. And it doesn't authorize anything, only delegates
the authorization (e.g. to gitolite).
> ...
> > And how many (relative) use client ceritificates with SSL? Keypairs with SSH?
> > Why you think this is?
>
> Because ssh is much more popular than ssl client auth. Obtaining client
> certificates isn't much more complicated than getting an ssh account,
> once you have scripts for the stuff ready.
SSL client certificate usability is horrible. SSH keypairs are actually
almost usable.
> But I wonder: When you want keypair auth, why not just use ssh?
IIRC, I already have told at least twice...
> I didn't quite understand the use case yet, it seems. With ssh
> I have all the infrastructure like ssh-agent in place already;
> with gits: (any kind of) it will be asked for sooner or later.
gpg-agent can be used (since client uses gpg to protect the keys
if needed).
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 20:13 ` Avery Pennarun
@ 2010-01-13 21:04 ` Ilari Liusvaara
2010-01-13 22:03 ` Avery Pennarun
0 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 21:04 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 03:13:40PM -0500, Avery Pennarun wrote:
> On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara
>
> Lots of people use it. That was my point. If it weren't important,
> web browser makers wouldn't bother putting it in; God knows they leave
> out a lot of other stuff that I'd like.
There are two kinds of "important". Actually important for users and
important for managers.
The latter tends to be implemented with much less real world need. And
one can usually tell which of those it was from usability of feature.
> >> Furthermore, how many people who really want ssh-style keypairs (and
> >> thus refuse to use X.509 and PKI) can't just use ssh as their git
> >> transport? I don't actually understand what the goal is here.
> >
> > As said, I got fed up with failure modes of SSH.
>
> I think this is the answer that needs clarification. What failure
> modes are these? ssh doesn't seem to fail for me. And github.com
> seems to be working rather well with a huge number of users and ssh
> authentication.
Those failure modes tend to be show up at setup phase. But when they
show up, at worst I have seen ones that took hours to debug because
of multitude of possible causes and no good information on what's
wrong.
And don't get me started about multi-key setups.
SSH uses fixed sets of keys, which has inherent failure modes. And ssh
server tends to be worse than the client (Github can avoid the server
failure modes since they control the SSH server).
But not even github can avoid all the failure modes.
> If you're upset at the failure modes of ssh, is it possible to fix ssh
> instead of introducing Yet Another Tunneling Protocol?
No, those failure modes can't be solved in SSH.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 21:04 ` Ilari Liusvaara
@ 2010-01-13 22:03 ` Avery Pennarun
2010-01-13 22:06 ` Shawn O. Pearce
2010-01-13 23:00 ` Ilari Liusvaara
0 siblings, 2 replies; 28+ messages in thread
From: Avery Pennarun @ 2010-01-13 22:03 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 4:04 PM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> On Wed, Jan 13, 2010 at 03:13:40PM -0500, Avery Pennarun wrote:
>> On Wed, Jan 13, 2010 at 3:06 PM, Ilari Liusvaara
>> > As said, I got fed up with failure modes of SSH.
>>
>> I think this is the answer that needs clarification. What failure
>> modes are these? ssh doesn't seem to fail for me. And github.com
>> seems to be working rather well with a huge number of users and ssh
>> authentication.
>
> Those failure modes tend to be show up at setup phase. But when they
> show up, at worst I have seen ones that took hours to debug because
> of multitude of possible causes and no good information on what's
> wrong.
>
> And don't get me started about multi-key setups.
>
> SSH uses fixed sets of keys, which has inherent failure modes. And ssh
> server tends to be worse than the client (Github can avoid the server
> failure modes since they control the SSH server).
>
> But not even github can avoid all the failure modes.
>
>> If you're upset at the failure modes of ssh, is it possible to fix ssh
>> instead of introducing Yet Another Tunneling Protocol?
>
> No, those failure modes can't be solved in SSH.
This is still not very illuminating. How do you know your replacement
will not have these same failure modes? If you solve your main
annoyances with ssh, how do you know you won't introduce any new
annoying failure modes? *Why* can't ssh be fixed to solve the
problem? Will I have to generate and manage yet another new set of
keys to use the new system?
You seem to be positioning your implementation as a competitor to
*all* of ssh, https, and straight TLS (including stunnel), and
moreover, presenting it as superior to all three. This is surely
possible (they all suck differently), but it's going to be hard to
convince people. And if your new security protocol *only* works with
git, it loses points automatically against other solutions. (Even if
ssh is hard to set up, I've *already set it up*, so any new
alternative starts with an immediate negative score.)
Have fun,
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 22:03 ` Avery Pennarun
@ 2010-01-13 22:06 ` Shawn O. Pearce
2010-01-13 23:00 ` Ilari Liusvaara
1 sibling, 0 replies; 28+ messages in thread
From: Shawn O. Pearce @ 2010-01-13 22:06 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Ilari Liusvaara, Andreas Krey, Nguyen Thai Ngoc Duy, git
Avery Pennarun <apenwarr@gmail.com> wrote:
> possible (they all suck differently), but it's going to be hard to
> convince people. And if your new security protocol *only* works with
> git, it loses points automatically against other solutions. (Even if
> ssh is hard to set up, I've *already set it up*, so any new
> alternative starts with an immediate negative score.)
Yup.
This is where I have trouble with gits:// thus far.
I already have SSH setup everywhere. Even more so than I have
GnuPG configured, because I need SSH for just about everything,
and GnuPG for very little. So, I might as well just use SSH.
--
Shawn.
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 22:03 ` Avery Pennarun
2010-01-13 22:06 ` Shawn O. Pearce
@ 2010-01-13 23:00 ` Ilari Liusvaara
2010-01-13 23:51 ` Avery Pennarun
1 sibling, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-13 23:00 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:
> On Wed, Jan 13, 2010 at 4:04 PM, Ilari Liusvaara
>
> This is still not very illuminating. How do you know your replacement
> will not have these same failure modes?
No client-side fallbacks, key auth works pseudonymously. That takes
care of them pretty well.
> If you solve your main
> annoyances with ssh, how do you know you won't introduce any new
> annoying failure modes?
Ensuring that at least some information make back to client (presuably
enough to figure out the problem).
And then there are few failure modes that can't be helped no matter what
I do (like mistaking protocol for P2P, git:// suffers from that as well).
> *Why* can't ssh be fixed to solve the problem?
Client side fallbacks (may be desired or not!), service not being
able to intervene on wheither to allow client or not in case of
keypair auth.
> Will I have to generate and manage yet another new set of
> keys to use the new system?
Yes.
> You seem to be positioning your implementation as a competitor to
> *all* of ssh, https, and straight TLS (including stunnel),
Only to smart http://, smart https:// and ssh://.
> and
> moreover, presenting it as superior to all three. This is surely
> possible (they all suck differently), but it's going to be hard to
> convince people. And if your new security protocol *only* works with
> git, it loses points automatically against other solutions.
The general design would be applicable to applications besides git, but
this implementation is git-specific.
And making it work like stunnel? On server-side that could work, but
not on client side (and it would take quite extensive changes to
git-daemon).
> (Even if
> ssh is hard to set up, I've *already set it up*, so any new
> alternative starts with an immediate negative score.)
Well, if you like SSH more, then use ssh://...
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 23:00 ` Ilari Liusvaara
@ 2010-01-13 23:51 ` Avery Pennarun
2010-01-14 8:51 ` Ilari Liusvaara
0 siblings, 1 reply; 28+ messages in thread
From: Avery Pennarun @ 2010-01-13 23:51 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 6:00 PM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:
>> This is still not very illuminating. How do you know your replacement
>> will not have these same failure modes?
>
> No client-side fallbacks, key auth works pseudonymously. That takes
> care of them pretty well.
Perhaps I'm being dense, but I don't understand what you mean by
either of those.
>> If you solve your main
>> annoyances with ssh, how do you know you won't introduce any new
>> annoying failure modes?
>
> Ensuring that at least some information make back to client (presuably
> enough to figure out the problem).
Unfortunately revealing information like that is a compromise; it
helps attackers as well as legitimate users. It's the same reason
login prints "invalid username or password" instead of choosing
between "invalid username" and "invalid password."
If you reveal more information than ssh, you'll be accused of being
less secure. And since the purpose of your protocol is security, this
is a problem.
>> *Why* can't ssh be fixed to solve the problem?
>
> Client side fallbacks (may be desired or not!), service not being
> able to intervene on wheither to allow client or not in case of
> keypair auth.
I don't understand that answer. Couldn't ssh be patched to do
whatever you want? Particularly if it's just better (optional)
diagnostics, you'd think someone would accept the patch for that.
>> Will I have to generate and manage yet another new set of
>> keys to use the new system?
>
> Yes.
Ouch.
>> (Even if
>> ssh is hard to set up, I've *already set it up*, so any new
>> alternative starts with an immediate negative score.)
>
> Well, if you like SSH more, then use ssh://...
I'm just looking for a justification for why I *shouldn't* like ssh
more. Is the only reason the fact that it might be easier to
initially configure the key exchange?
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-13 23:51 ` Avery Pennarun
@ 2010-01-14 8:51 ` Ilari Liusvaara
2010-01-14 20:46 ` Avery Pennarun
0 siblings, 1 reply; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-14 8:51 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Wed, Jan 13, 2010 at 06:51:03PM -0500, Avery Pennarun wrote:
> On Wed, Jan 13, 2010 at 6:00 PM, Ilari Liusvaara
> <ilari.liusvaara@elisanet.fi> wrote:
> > On Wed, Jan 13, 2010 at 05:03:45PM -0500, Avery Pennarun wrote:
> >
> > No client-side fallbacks, key auth works pseudonymously. That takes
> > care of them pretty well.
>
> Perhaps I'm being dense, but I don't understand what you mean by
> either of those.
The client tries only one auth method instead of potentially trying
multiple. Witness the 'use verbose mode and check if it uses the key'
type stuff.
With keypair auth, the server can accept arbitrary (valid) keypair,
but only limited set have special priviledges -> Cuts down significantly
on "why this doesn't accept the key" problems (the keyid is usually
printed on denied access).
> >> If you solve your main
> >> annoyances with ssh, how do you know you won't introduce any new
> >> annoying failure modes?
> >
> > Ensuring that at least some information make back to client (presuably
> > enough to figure out the problem).
>
> Unfortunately revealing information like that is a compromise; it
> helps attackers as well as legitimate users. It's the same reason
> login prints "invalid username or password" instead of choosing
> between "invalid username" and "invalid password."
Yeah. Sometimes one must chose balance between being helpful to users
and being helpful for attackers.
> >> *Why* can't ssh be fixed to solve the problem?
> >
> > Client side fallbacks (may be desired or not!), service not being
> > able to intervene on wheither to allow client or not in case of
> > keypair auth.
>
> I don't understand that answer. Couldn't ssh be patched to do
> whatever you want? Particularly if it's just better (optional)
> diagnostics, you'd think someone would accept the patch for that.
OpenSSH? With the level of paranoia in it, I'd say good luck. And
it's not just client, its the server also (and especially the
server).
> >> Will I have to generate and manage yet another new set of
> >> keys to use the new system?
> >
> > Yes.
>
> Ouch.
Well, usually that means one keypair to generate and exchanging
keyids.
And if you host the repo system too, you would get second key anyway
(and SSH is not too good at handling multiple keys).
> > Well, if you like SSH more, then use ssh://...
>
> I'm just looking for a justification for why I *shouldn't* like ssh
> more. Is the only reason the fact that it might be easier to
> initially configure the key exchange?
And besides, gits:// is for host multiple repos type stuff, not for
private repos on your account (use the ssh:// for those, and there the
failure modes of SSH matter much less).
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-14 8:51 ` Ilari Liusvaara
@ 2010-01-14 20:46 ` Avery Pennarun
2010-01-14 23:08 ` Ilari Liusvaara
0 siblings, 1 reply; 28+ messages in thread
From: Avery Pennarun @ 2010-01-14 20:46 UTC (permalink / raw)
To: Ilari Liusvaara; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Thu, Jan 14, 2010 at 3:51 AM, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> The client tries only one auth method instead of potentially trying
> multiple. Witness the 'use verbose mode and check if it uses the key'
> type stuff.
I believe this is a limitation of the client, not of the protocol. So
a patch to the ssh client could fix this.
> OpenSSH? With the level of paranoia in it, I'd say good luck. And
> it's not just client, its the server also (and especially the
> server).
But you could fork it if you wanted. It's about as easy to convince
me to install a different version of ssh than to install
yet-another-security-server. (In fact, it might be easier to get me
to put in a patched openssh; at least then I can trust that it's
mostly openssh, and examine just what's different in your version.)
> And if you host the repo system too, you would get second key anyway
> (and SSH is not too good at handling multiple keys).
I'm not really sure about this. ssh-add seems pretty easy.
Have fun,
Avery
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
2010-01-14 20:46 ` Avery Pennarun
@ 2010-01-14 23:08 ` Ilari Liusvaara
0 siblings, 0 replies; 28+ messages in thread
From: Ilari Liusvaara @ 2010-01-14 23:08 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Andreas Krey, Nguyen Thai Ngoc Duy, git
On Thu, Jan 14, 2010 at 03:46:56PM -0500, Avery Pennarun wrote:
> On Thu, Jan 14, 2010 at 3:51 AM, Ilari Liusvaara
> <ilari.liusvaara@elisanet.fi> wrote:
> > The client tries only one auth method instead of potentially trying
> > multiple. Witness the 'use verbose mode and check if it uses the key'
> > type stuff.
>
> I believe this is a limitation of the client, not of the protocol. So
> a patch to the ssh client could fix this.
This is also about interfaces to user. It effectively can't be patched.
<forking OpenSSH>
Get real.
> > And if you host the repo system too, you would get second key anyway
> > (and SSH is not too good at handling multiple keys).
>
> I'm not really sure about this. ssh-add seems pretty easy.
Anyway, two SSH keys in interactive use means which to use has to be selected,
one doesn't.
-Ilari
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2010-01-14 23:08 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 " 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 13:57 ` Ilari Liusvaara
2010-01-13 14:12 ` Andreas Krey
2010-01-13 14:47 ` Ilari Liusvaara
2010-01-13 16:17 ` Andreas Krey
2010-01-13 17:36 ` Ilari Liusvaara
2010-01-13 18:35 ` Andreas Krey
2010-01-13 19:18 ` Ilari Liusvaara
2010-01-13 19:30 ` Avery Pennarun
2010-01-13 20:06 ` Ilari Liusvaara
2010-01-13 20:13 ` Avery Pennarun
2010-01-13 21:04 ` Ilari Liusvaara
2010-01-13 22:03 ` Avery Pennarun
2010-01-13 22:06 ` Shawn O. Pearce
2010-01-13 23:00 ` Ilari Liusvaara
2010-01-13 23:51 ` Avery Pennarun
2010-01-14 8:51 ` Ilari Liusvaara
2010-01-14 20:46 ` Avery Pennarun
2010-01-14 23:08 ` Ilari Liusvaara
2010-01-13 19:40 ` Andreas Krey
2010-01-13 20:47 ` Ilari Liusvaara
2010-01-13 19:11 ` Avery Pennarun
2010-01-13 20:00 ` Ilari Liusvaara
2010-01-13 20:13 ` Edward Z. Yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).