[RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2)
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