[PATCH 2/3] git over TLS (gits://) support (part 2)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Ilari Liusvaara <ilari.liusvaara@xxxxxxxxxxx>
---
 git-over-tls/.gitignore                      |    5 +
 git-over-tls/base64.c                        |  171 +++++++
 git-over-tls/base64.h                        |   21 +
 git-over-tls/certificate.c                   |  306 ++++++++++++
 git-over-tls/certificate.h                   |   28 +
 git-over-tls/connect.c                       |  275 +++++++++++
 git-over-tls/connect.h                       |   17 +
 git-over-tls/genkeypair.c                    |   38 ++
 git-over-tls/gensrpverifier.c                |  377 ++++++++++++++
 git-over-tls/getkeyid.c                      |  179 +++++++
 git-over-tls/gits-send-special-command       |   22 +
 git-over-tls/gits-send-special-command-nourl |   23 +
 git-over-tls/home.c                          |  229 +++++++++
 git-over-tls/home.h                          |   71 +++
 git-over-tls/hostkey.c                       |   81 +++
 git-over-tls/hostkey.h                       |   15 +
 git-over-tls/keypairs.c                      |   60 +++
 git-over-tls/keypairs.h                      |   16 +
 git-over-tls/main.c                          |  684 ++++++++++++++++++++++++++
 git-over-tls/misc.c                          |   15 +
 git-over-tls/misc.h                          |   27 +
 git-over-tls/mkcert.c                        |  474 ++++++++++++++++++
 git-over-tls/prompt.c                        |  100 ++++
 git-over-tls/prompt.h                        |   18 +
 git-over-tls/srp_askpass.c                   |  110 ++++
 git-over-tls/srp_askpass.h                   |   16 +
 26 files changed, 3378 insertions(+), 0 deletions(-)
 create mode 100644 git-over-tls/.gitignore
 create mode 100644 git-over-tls/base64.c
 create mode 100644 git-over-tls/base64.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 100755 git-over-tls/gits-send-special-command-nourl
 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/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

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/base64.c b/git-over-tls/base64.c
new file mode 100644
index 0000000..dbf5334
--- /dev/null
+++ b/git-over-tls/base64.c
@@ -0,0 +1,171 @@
+#include "base64.h"
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+void encode_uint32(unsigned char *ptr, unsigned long value)
+{
+	ptr[0] = (unsigned char)(value >> 24);
+	ptr[1] = (unsigned char)(value >> 16);
+	ptr[2] = (unsigned char)(value >> 8);
+	ptr[3] = (unsigned char)(value);
+}
+
+/* Base64 character value */
+static int char_value(unsigned char ch)
+{
+	if (ch >= 'A' && ch <= 'Z')
+		return ch - 'A';
+	if (ch >= 'a' && ch <= 'z')
+		return ch - 'a' + 26;
+	if (ch >= '0' && ch <= '9')
+		return ch - '0' + 52;
+	if (ch == '+')
+		return 62;
+	if (ch == '/')
+		return 63;
+	return -1;
+}
+
+/* Decode Base64 chunk. */
+unsigned char *decode_base64_chunk(const unsigned char *chunk,
+	size_t chunk_len, size_t *key_len)
+{
+	unsigned char *ret;
+	unsigned blockmod = 0;
+	int buf[4];
+	size_t i;
+
+	ret = xmalloc((chunk_len + 3) / 4 * 3);
+	*key_len = 0;
+
+	for(i = 0; i < chunk_len; i++) {
+		buf[blockmod] = char_value(chunk[i]);
+		if (buf[blockmod] >= 0)
+			blockmod++;
+		else
+			buf[blockmod] = 0;
+		if(blockmod == 4) {
+			int v = (buf[0] << 18) | (buf[1] << 12) |
+				(buf[2] << 6) | buf[3];
+			ret[(*key_len)++] = (unsigned char)(v >> 16);
+			ret[(*key_len)++] = (unsigned char)(v >> 8);
+			ret[(*key_len)++] = (unsigned char)(v);
+			blockmod = 0;
+			buf[0] = buf[1] = buf[2] = buf[3] = 0;
+		}
+	}
+	if(blockmod > 0) {
+		int v = (buf[0] << 18) | (buf[1] << 12) |
+			(buf[2] << 6) | buf[3];
+		if (blockmod > 1)
+			ret[(*key_len)++] = (unsigned char)(v >> 16);
+		if (blockmod > 2)
+			ret[(*key_len)++] = (unsigned char)(v >> 8);
+	}
+	return ret;
+}
+
+/* Return address of next (nonempty) line, or NULL if none. */
+const unsigned char *next_line(const unsigned char *blob,
+	size_t *remaining)
+{
+	while (*blob != '\r' && *blob != '\n') {
+		blob++;
+		(*remaining)--;
+		if (!*remaining)
+			return NULL;
+	}
+	while (*blob == '\r' || *blob == '\n') {
+		blob++;
+		(*remaining)--;
+		if (!*remaining)
+			return NULL;
+	}
+	return blob;
+}
+
+#define STATUS_INIT 0
+#define STATUS_HEADER 1
+#define STATUS_CONTINUE 2
+#define STATUS_EOL 3
+#define STATUS_EOL_CONTINUE 4
+
+/* Find start of base64 blob. */
+const unsigned char *base64_blob_start(const unsigned char *blob,
+	size_t *remaining)
+{
+	int status = STATUS_INIT;
+	const unsigned char *line_start;
+	size_t size_start;
+
+	line_start = blob;
+	size_start = *remaining;
+
+	while(1) {
+		switch(status) {
+		case STATUS_INIT:
+			if (!*remaining || *blob == '\r' || *blob == '\n') {
+				/* Back off to start of line. */
+				blob = line_start;
+				*remaining = size_start;
+				return blob;
+			}
+			if (*blob == ':')
+				status = STATUS_HEADER;
+			break;
+		case STATUS_HEADER:
+			if (!*remaining) {
+				*remaining = 0;
+				return NULL;
+			}
+			if (*blob == '\r' || *blob == '\n')
+				status = STATUS_EOL;
+			if (*blob == '\\')
+				status = STATUS_CONTINUE;
+			break;
+		case STATUS_CONTINUE:
+			if (!*remaining) {
+				*remaining = 0;
+				return NULL;
+			}
+			if (*blob == '\r' || *blob == '\n')
+				status = STATUS_EOL_CONTINUE;
+			else
+				status = STATUS_HEADER;
+			break;
+		case STATUS_EOL:
+			if (!*remaining) {
+				*remaining = 0;
+				return NULL;
+			}
+			if (*blob != '\r' && *blob != '\n') {
+				/* Mark line start and back off by one. */
+				line_start = blob;
+				size_start = *remaining;
+				blob--;
+				(*remaining)++;
+				status = STATUS_INIT;
+			}
+			break;
+		case STATUS_EOL_CONTINUE:
+			if (!*remaining) {
+				*remaining = 0;
+				return NULL;
+			}
+			if (*blob != '\r' && *blob != '\n') {
+				/* Mark line start and back off by one. */
+				line_start = blob;
+				size_start = *remaining;
+				blob--;
+				(*remaining)++;
+				status = STATUS_HEADER;
+			}
+			break;
+		}
+		blob++;
+		(*remaining)--;
+	}
+}
diff --git a/git-over-tls/base64.h b/git-over-tls/base64.h
new file mode 100644
index 0000000..b4352b1
--- /dev/null
+++ b/git-over-tls/base64.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) Ilari Liusvaara 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 _base64__h__included__
+#define _base64__h__included__
+
+#include <stdlib.h>
+
+unsigned char *decode_base64_chunk(const unsigned char *chunk,
+	size_t chunk_len, size_t *key_len);
+const unsigned char *base64_blob_start(const unsigned char *blob,
+	size_t *remaining);
+const unsigned char *next_line(const unsigned char *blob,
+	size_t *remaining);
+void encode_uint32(unsigned char *ptr, unsigned long value);
+
+#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..f16c7b6
--- /dev/null
+++ b/git-over-tls/connect.c
@@ -0,0 +1,275 @@
+/*
+ * 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>
+#include <arpa/inet.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
+
+#define BUFFERLEN 256
+
+extern int verbose;
+
+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);
+	if (verbose) {
+		fprintf(stderr, "Connecting to unix socket '%s'...",
+			path);
+		fflush(stderr);
+	}
+	ret = connect(fd, (struct sockaddr*)&saddru, plen);
+	if (ret < 0) {
+		if (verbose)
+			fprintf(stderr, "%s\n", strerror(errno));
+		die_errno("Can't connect to '%s'", path);
+	}
+	if (verbose)
+		fprintf(stderr, "Success.\n");
+	return fd;
+#else
+	die("Unix domain sockets not supported by this build");
+#endif
+}
+
+static int connect_address(const char *host, const char *port,
+	int protocol, struct sockaddr *addr, int size)
+{
+	char address[1024];
+	char nport[256];
+	int fd, ret;
+
+	fd = socket(addr->sa_family, SOCK_STREAM, protocol);
+	if (fd < 0) {
+		error("Can't create socket: %s", strerror(errno));
+		return -1;
+	}
+
+#ifndef NO_IPV6
+	int r = getnameinfo(addr, size, address, 1024, nport, 256,
+		NI_NUMERICSERV | NI_NUMERICHOST);
+	if (r)
+		strcpy(address, "<parse error>");
+#else
+	strcpy(address, inet_ntoa(((struct sockaddr_in*)addr)->sin_addr));
+	sprintf(nport, "%u", (unsigned)ntohs(((struct sockaddr_in*)addr)->
+		sin_port));
+#endif
+	if (verbose) {
+		fprintf(stderr, "Connecting to address '%s', port %s...",
+			address, nport);
+		fflush(stderr);
+	}
+	ret = connect(fd, addr, size);
+	if (ret < 0) {
+		fprintf(stderr, "%s\n", strerror(errno));
+		return -1;
+	}
+	if (verbose)
+		fprintf(stderr, "Success\n");
+	return fd;
+}
+
+int connect_gethostbyname(const char *_host,
+	const char *port, const char *_protocol, const char *_family)
+{
+	int protocol = 0;
+	int family = 0;
+	struct protoent* proto;
+
+	if (!_family)
+		family = 0;
+	else if (!strcmp(_family, "ipv4"))
+		family = AF_INET;
+#ifndef NO_IPV6
+	else if (!strcmp(_family, "ipv6"))
+		family = AF_INET6;
+#endif
+	else
+		die("Unknown address type '%s'", _family);
+
+	if (verbose) {
+		fprintf(stderr, "Resolving protocol '%s'...", _protocol);
+		fflush(stderr);
+	}
+
+	proto = getprotobyname(_protocol);
+	if (!proto) {
+		if (verbose)
+			fprintf(stderr, "Failed\n");
+		die("Unknown protocol '%s'", _protocol);
+	}
+	protocol = proto->p_proto;
+
+	if (verbose)
+		fprintf(stderr, "Protocol #%i\n", protocol);
+
+#ifndef NO_IPV6
+	struct addrinfo hints;
+	struct addrinfo *returned;
+	int fd, ret;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_family = family;
+	hints.ai_protocol = protocol;
+
+	if (verbose) {
+		fprintf(stderr, "Resolving host '%s', port '%s'...",
+			_host, port);
+		fflush(stderr);
+	}
+
+	ret = getaddrinfo(_host, port, &hints, &returned);
+	if (ret) {
+		if (verbose)
+			fprintf(stderr, "%s\n", gai_strerror(ret));
+		die("Can't resolve address: %s", gai_strerror(ret));
+	}
+
+	if (verbose) {
+		fprintf(stderr, "Success\n");
+	}
+
+	while (returned) {
+		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;
+	int _port;
+	unsigned long _port2;
+	struct servent* serv;
+	char* end;
+
+	if (verbose) {
+		fprintf(stderr, "Resolving service '%s/%s'...", port,
+			_protocol);
+		fflush(stderr);
+	}
+
+	_port2 = strtoul(port, &end, 10);
+	serv = getservbyname(port, _protocol);
+	if (!serv && (_port2 < 1 || _port2 > 65535 || *end)) {
+		if (verbose)
+			fprintf(stderr, "Not found\n");
+		die("No such port or service '%s/%s'", port,
+			_protocol);
+	} else if (serv)
+		_port = serv->s_port;
+	else
+		_port = (int)_port2;
+
+	if (verbose)
+		fprintf(stderr, "Port %u\n", (unsigned)_port);
+
+	if (verbose) {
+		fprintf(stderr, "Resolving host '%s'...", _host);
+		fflush(stderr);
+	}
+
+	host = gethostbyname(_host);
+	if (!host || !host->h_addr) {
+		if (verbose)
+			fprintf(stderr, "Failed\n");
+		die("Can't find host");
+	}
+
+	if (verbose)
+		fprintf(stderr, "Success\n");
+
+	if (host->h_addrtype != AF_INET)
+		die("Can't handle address type in result");
+
+next_address:
+	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 (fd >= 0)
+		goto out;
+
+	host->h_addr_list++;
+	if (host->h_addr_list[0])
+		goto next_address;
+
+	die("Can't connect to host %s", _host);
+out:
+	return fd;
+#endif
+}
+
+int connect_host(const char *_host, const char *port, const char *tproto,
+	const char *addrspace)
+{
+	char *hostcopy;
+
+	if (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/') ||
+		(addrspace && !strcmp(addrspace, "unix")))
+		return connect_unix(_host);
+
+	hostcopy = xmalloc(strlen(_host) + 1);
+	strcpy(hostcopy, _host);
+
+	return connect_gethostbyname(hostcopy, port, tproto, addrspace);
+}
diff --git a/git-over-tls/connect.h b/git-over-tls/connect.h
new file mode 100644
index 0000000..bc861be
--- /dev/null
+++ b/git-over-tls/connect.h
@@ -0,0 +1,17 @@
+/*
+ * 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__
+
+#include <stdlib.h>
+
+//Returns connected fd or dies.
+int connect_host(const char *host, const char *port,
+	const char *tproto, const char *addrspace);
+
+#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..3d1decb
--- /dev/null
+++ b/git-over-tls/gensrpverifier.c
@@ -0,0 +1,377 @@
+/*
+ * 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>
+#include <string.h>
+#include "home.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) {
+		int fd;
+		fd = open_create_dirs(file, O_CREAT | O_EXCL | O_WRONLY, 0600);
+		if (fd < 0) {
+			fprintf(stderr, "Can't open '%s': %s\n", file,
+				strerror(errno));
+			goto file_again;
+		}
+		filp = xfdopen(fd, "w");
+	}
+
+	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..baf394f
--- /dev/null
+++ b/git-over-tls/getkeyid.c
@@ -0,0 +1,179 @@
+/*
+ * 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 "ssh.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#include <gcrypt.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("--ssh\n");
+	printf("\tRead SSH keys\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);
+}
+
+#define MAXKEY 65536
+#define HASHBUFFERSIZE 28
+#define HASHFUNCTION GCRY_MD_SHA224
+
+void dump_blob(const char *name, const unsigned char *x, size_t i);
+
+int do_ssh_key(const char *name)
+{
+	FILE* filp;
+	int s;
+	char filename[PATH_MAX + 1];
+	unsigned char key[MAXKEY];
+	size_t keysize;
+	char *path;
+	unsigned char *x;
+	size_t i;
+	unsigned char hbuffer[HASHBUFFERSIZE];
+
+	if (strchr(name, '/'))
+		s = snprintf(filename, PATH_MAX + 1, "%s", name);
+	else
+		s = snprintf(filename, PATH_MAX + 1, "~/.ssh/%s.pub",
+			name);
+	if (s < 0 || s > PATH_MAX)
+		die("Insanely long keyname");
+
+	path = expand_path(filename);
+
+	/* Read the pubkey file. */
+	filp = fopen(path, "r");
+	if (!filp)
+		die("Can't open '%s'", path);
+	keysize = fread(key, 1, MAXKEY, filp);
+	if (ferror(filp))
+		die("Can't read '%s'", path);
+	if (!feof(filp))
+		die("Keyfile '%s' too large", path);
+	fclose(filp);
+
+	x = extract_key_from_file(key, keysize, &i);
+
+	gcry_md_hash_buffer(HASHFUNCTION, hbuffer, x, i);
+
+	printf("ssh-");
+	for (s = 0; s < HASHBUFFERSIZE; s++)
+		printf("%02x", hbuffer[s]);
+	printf("\n");
+	return 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];
+	char *filename2;
+	unsigned char key[KEYBUF];
+	int s;
+	unsigned vout;
+	size_t vout2;
+
+	if (argc < 2 || argc > 3) {
+		fprintf(stderr, "syntax: %s <keyname>\n", argv[0]);
+		fprintf(stderr, "syntax: %s <keyfile>\n", argv[0]);
+		fprintf(stderr, "syntax: %s --ssh <keyname>\n", argv[0]);
+		fprintf(stderr, "syntax: %s --ssh <keyfile>\n", argv[0]);
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "--help"))
+		do_help();
+
+	s = gnutls_global_init();
+	if (s < 0)
+		die("Can't initialize GnuTLS: %s",
+			gnutls_strerror(s));
+
+	if (!strcmp(argv[1], "--ssh"))
+		return do_ssh_key(argv[2]);
+
+	if (strchr(argv[1], '/'))
+		s = snprintf(filename, PATH_MAX + 1, "%s", argv[1]);
+	else
+		s = snprintf(filename, PATH_MAX + 1, "$XDG_CONFIG_HOME/.gits/keys/%s",
+			argv[1]);
+	if (s < 0 || s > PATH_MAX)
+		die("Insanely long keyname");
+
+
+	filename2 = expand_path(filename);
+	certificate = parse_certificate(filename2, &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");
+
+	vout2 = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout2);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	printf("openpgp-");
+	for (s = 0; s < (int)vout2; 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/gits-send-special-command-nourl b/git-over-tls/gits-send-special-command-nourl
new file mode 100755
index 0000000..e9de10a
--- /dev/null
+++ b/git-over-tls/gits-send-special-command-nourl
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# 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-nourl: Send special command to "
+	echo "server with no explicit path"
+	echo "command line:"
+	echo "--help"
+	echo -e "\x09This help"
+	echo "<service> <URL>"
+	echo -e "\x09Send request for <service> to specified <URL> the "
+	echo -e "\x09path part of URL is ignored."
+	exit 0
+fi
+
+git-remote-gits --nourl-service=$1 $2
diff --git a/git-over-tls/home.c b/git-over-tls/home.c
new file mode 100644
index 0000000..1d2b954
--- /dev/null
+++ b/git-over-tls/home.c
@@ -0,0 +1,229 @@
+/*
+ * 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 "home.h"
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static 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;
+}
+
+static char *replace_variable(char *path, const char *value)
+{
+	char *slash;
+	char *oldpath = path;
+
+	slash = strchr(path, '/');
+	if (slash) {
+		size_t len;
+		len = strlen(path) - (slash - path) +
+			strlen(value);
+		path = xmalloc(len + 1);
+		if (value[strlen(value) - 1] == '/')
+			sprintf(path, "%s%s", value, slash + 1);
+		else
+			sprintf(path, "%s%s", value, slash);
+	} else {
+		size_t len;
+		len = strlen(value);
+		path = xstrdup(value);
+		if (path[len - 1] == '/')
+			path[len - 1] = '\0';
+	}
+	free(oldpath);
+	return path;
+}
+
+int check_component(const char *path, size_t len)
+{
+	struct stat s;
+	int r;
+	char *tmp;
+	tmp = xstrndup(path, len);
+
+	r = stat(tmp, &s);
+	if (r < 0 && errno != ENOENT) {
+		error("Creating '%s', unable to stat '%s': %s",
+			path, tmp, strerror(errno));
+		return -1;
+	} else if (r < 0) {
+		/* Attempt to create. */
+		int r;
+		r = mkdir(tmp, 0700);
+		if (r >= 0)
+			goto end;
+		error("Creating '%s', unable to create '%s': %s",
+			path, tmp, strerror(errno));
+		return -1;
+	} else if (S_ISDIR(s.st_mode)) {
+		/* Exists and directory, OK. */
+	} else {
+		error("Creating '%s', '%s' exists but is not a directory",
+			path, tmp);
+		return -1;
+	}
+end:
+	free(tmp);
+	return 0;
+}
+
+static int ensure_directory_real(const char *path)
+{
+	size_t index;
+	size_t last_index = 0;
+
+	/* CWD always exists. */
+	if (!*path)
+		return 0;
+
+	for (index = 0; path[index]; index++) {
+		/* In middle of name? */
+		if (path[index] != '/')
+			continue;
+		/* Root always exists. */
+		if (index == 0)
+			continue;
+		if (check_component(path, index) < 0)
+			return -1;
+		last_index = index;
+	}
+	if (index > last_index + 1)
+		if (check_component(path, index) < 0)
+			return -1;
+	return 0;
+}
+
+char *expand_path(const char *path)
+{
+	char *path2;
+	path2 = xstrdup(path);
+
+	/*
+	 * Handle $XDG_CONFIG_HOME and $XDG_DATA_HOME first, as
+	 * these may use $HOME.
+	 */
+	if (!strcmp(path2, "$XDG_CONFIG_HOME") ||
+		!strncmp(path2, "$XDG_CONFIG_HOME/", 17)) {
+		const char* var;
+
+		var = getenv("XDG_CONFIG_HOME");
+		if (!var || !*var)
+			var = "$HOME/.config/";
+		path2 = replace_variable(path2, var);
+	}
+	if (!strcmp(path2, "$XDG_DATA_HOME") ||
+		!strncmp(path2, "$XDG_DATA_HOME/", 15)) {
+		const char* var;
+
+		var = getenv("XDG_DATA_HOME");
+		if (!var || !*var)
+			var = "$HOME/.local/share";
+		path2 = replace_variable(path2, var);
+	}
+	if (!strcmp(path2, "$HOME") ||
+		!strncmp(path2, "$HOME/", 6)) {
+		const char* var;
+
+		var = get_home();
+		path2 = replace_variable(path2, var);
+	}
+	if (!strcmp(path2, "~") ||
+		!strncmp(path2, "~/", 2)) {
+		const char* var;
+
+		var = get_home();
+		path2 = replace_variable(path2, var);
+	}
+	return path2;
+}
+
+int ensure_directory(const char *path)
+{
+	char *path2;
+	int r;
+
+	path2 = expand_path(path);
+	r = ensure_directory_real(path2);
+	free(path2);
+	return r;
+}
+
+int open_create_dirs(const char *path, int flags, mode_t mode)
+{
+	int r;
+	struct stat s;
+	char *path2;
+	char *path3 = NULL;
+	char *slash;
+
+	path2 = expand_path(path);
+
+	r = stat(path2, &s);
+	if (r < 0 && errno != ENOENT) {
+		error("Can't open/create '%s': stat failed: %s",
+			path2, strerror(errno));
+		return -1;
+	} else if (r < 0 && (flags & O_CREAT) == 0) {
+		errno = ENOENT;
+		return -1;
+	} else if (r >= 0 && !S_ISREG(s.st_mode)) {
+		error("Can't open/create '%s': It isn't regular file", path2);
+		errno = EEXIST;
+		return -1;
+	}
+
+	slash = strrchr(path2, '/');
+	if(slash) {
+		path3 = xstrndup(path2, slash - path2);
+		if (ensure_directory_real(path3) < 0)
+			return -1;
+	}
+
+	r = open(path2, flags, mode);
+
+	free(path2);
+	free(path3);
+
+	return r;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/home.h
new file mode 100644
index 0000000..842cd8c
--- /dev/null
+++ b/git-over-tls/home.h
@@ -0,0 +1,71 @@
+/*
+ * 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 _home__h__included__
+#define _home__h__included__
+
+#include <sys/types.h>
+
+/*
+ * Ensure that directory specified exists. If it does not, attempt
+ * to create it.
+ *
+ *Input:
+ *	path		The directory to ensure.
+ *
+ *Output:
+ *	Return value	0 on success, -1 on failure.
+ *
+ *Notes:
+ *	- Path starting with $HOME is special, it is
+ *	  relative to user home directory.
+ *	- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME
+ *	  are special. Those are interpretted as relative to XDG
+ *	  base dir spec directories.
+ */
+int ensure_directory(const char *path);
+
+/*
+ * Open the specified regular file. Leading path components are
+ * automatically created if file needs to be created.
+ *
+ *Input:
+ *	path		The file to open.
+ *	flags		Flags to pass to open.
+ *	mode		Mode to pass to open.
+ *
+ *Output:
+ *	Return value	File descriptor. -1 on failure.
+ *
+ *Notes:
+ *	- Path starting with $HOME is special, it is
+ *	  relative to user home directory.
+ *	- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME
+ *	  are special. Those are interpretted as relative to XDG
+ *	  base dir spec directories.
+ */
+int open_create_dirs(const char *path, int flags, mode_t mode);
+
+/*
+ * Expand the specified path.
+ *
+ *Input:
+ *	path		The path to expand.
+ *
+ *Output:
+ *	Return value	Mallocced copy of expanded path.
+ *
+ *Notes:
+ *	- Path starting with $HOME is special, it is
+ *	  relative to user home directory.
+ *	- Paths starting with $XDG_CONFIG_HOME and $XDG_DATA_HOME
+ *	  are special. Those are interpretted as relative to XDG
+ *	  base dir spec directories.
+ */
+char *expand_path(const char *path);
+
+#endif
diff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c
new file mode 100644
index 0000000..715bd8a
--- /dev/null
+++ b/git-over-tls/hostkey.c
@@ -0,0 +1,81 @@
+/*
+ * 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 129
+
+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;
+	size_t vout2;
+	unsigned char key[KEYBUF] = {0};
+	char keydec[2 * KEYBUF + 1] = {0};
+
+	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");
+
+	vout2 = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout2);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	/*
+	 * Be nice to users and strip this in case it gets
+	 * retained from key id calculator.
+	 */
+	if (!strncmp(hostname, "openpgp-", 8))
+		hostname += 8;
+
+
+	for (s = 0; s < vout2; s++)
+		sprintf(keydec + 2 * s, "%02x", (int)key[s]);
+	if (strcmp(hostname, keydec))
+		goto mismatch;
+	return;
+mismatch:
+	die("HOST KEY MISMATCH FOR HOST ('%s' vs '%s')!", hostname, keydec);
+}
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/keypairs.c b/git-over-tls/keypairs.c
new file mode 100644
index 0000000..20f94dc
--- /dev/null
+++ b/git-over-tls/keypairs.c
@@ -0,0 +1,60 @@
+/*
+ * 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 "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];
+	char *keypath2;
+	struct certificate cert;
+	int r;
+
+	r = snprintf(keypath, PATH_MAX + 1, "$XDG_CONFIG_HOME/gits/keys/%s",
+		username);
+	if (r < 0 || r > PATH_MAX) {
+		die("Username too long");
+	}
+
+	keypath2 = expand_path(keypath);
+	cert = parse_certificate(keypath2, &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));
+
+	free(keypath2);
+	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
diff --git a/git-over-tls/main.c b/git-over-tls/main.c
new file mode 100644
index 0000000..9bc76b2
--- /dev/null
+++ b/git-over-tls/main.c
@@ -0,0 +1,684 @@
+/*
+ * 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 "ssh.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <gnutls/gnutls.h>
+#include <signal.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+volatile sig_atomic_t in_handler = 0;
+volatile sig_atomic_t sigusr1_flag = 0;
+struct user *debug_for = NULL;
+int verbose = 0;
+static int socket_fd;
+
+static void sigusr1_handler(int x)
+{
+	x = 0;
+	if (in_handler)
+		sigusr1_flag = 1;
+	else
+		user_debug(debug_for, stderr);
+}
+
+struct parsed_addr
+{
+	char *protocol;		/* Protocol part */
+	char *user;		/* User part, NULL if no user */
+	char *host;		/* Hostname */
+	char *uservid;		/* Unique server ID */
+	char *port;		/* Port as string, NULL if no port */
+	char *path;		/* Path part. */
+	char *vhost_header;	/* vhost header to send */
+	char *transport_proto;	/* Transport protocol. */
+	char *address_space;	/* Address space to use. May be NULL */
+};
+
+void append_uniq_address(char *buffer, struct parsed_addr *_addr,
+	int default_flag)
+{
+	char *ptr;
+	ptr = strchr(_addr->host, '~');
+	if (!ptr)
+		ptr = _addr->host;
+	else
+		ptr++;
+	if (strchr(ptr, ':'))
+		strcat(buffer, "[");
+	strcat(buffer, ptr);
+	if (strchr(_addr->host, ':'))
+		strcat(buffer, "]");
+	if(!default_flag) {
+		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;
+	const char *proto_sep;
+	size_t addrlen;
+	int nondefault_port = 0;
+	size_t vhost_len;
+
+	_addr.transport_proto = xstrdup("tcp");
+	_addr.address_space = NULL;
+
+	addrlen = strlen(addr);
+
+	proto_end = strchr(addr, ':');
+	if (!proto_end)
+		die("URL '%s': No ':' to end protocol.", orig_addr);
+
+	_addr.protocol = xstrndup(addr, proto_end - addr);
+	if (strncmp(proto_end, "://", 3))
+		die("URL '%s': No '://' to end protocol.", orig_addr);
+
+	uhp_start = proto_end + 3;
+
+	/* Figure out the user if any. */
+	uhp_delim = strpbrk(uhp_start, "@[:/");
+
+	if (*uhp_delim == '@') {
+		_addr.user = xstrndup(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 = xstrndup(uhp_start + 1,
+				uhp_delim - uhp_start - 1);
+			if (uhp_delim[1] != ':' && uhp_delim[1] != '/')
+				die("URL '%s': Expected port or path after hostname",
+					orig_addr);
+			uhp_start = uhp_delim + 1;
+		} else
+			die("URL '%s': Hostname has '[' without matching ']'",
+				orig_addr);
+	} else {
+		uhp_delim = strpbrk(uhp_start, "[:/");
+		if (*uhp_delim == '[')
+			die("URL '%s': Unexpected '['", orig_addr);
+		_addr.host = xstrndup(uhp_start, uhp_delim - uhp_start);
+		uhp_start = uhp_delim;
+	}
+
+	_addr.uservid = NULL;
+	proto_sep = strchr(_addr.host, '@');
+	if (proto_sep && proto_sep != _addr.host)
+	{
+		char *old_host;
+		old_host = _addr.host;
+		_addr.uservid = xstrndup(_addr.host, proto_sep - _addr.host);
+		_addr.host = xstrdup(proto_sep + 1);
+		free(old_host);
+	}
+
+
+	proto_sep = strchr(_addr.host, '~');
+	if (proto_sep)
+	{
+		free(_addr.transport_proto);
+		free(_addr.address_space);
+
+		const char *ptr2;
+		char *old_host;
+		old_host = _addr.host;
+		ptr2 = strchr(old_host, '/');
+		if (ptr2 >= proto_sep)
+			ptr2 = NULL;
+
+		if (ptr2) {
+			_addr.transport_proto = xstrndup(_addr.host,
+				ptr2 - old_host);
+			_addr.address_space = xstrndup(ptr2 + 1,
+				proto_sep - ptr2 - 1);
+		} else if (proto_sep == old_host + 4 &&
+			!strncmp(old_host, "unix", 4)) {
+			_addr.transport_proto = xstrdup("<N/A>");
+			_addr.address_space = xstrdup("unix");
+		} else {
+			char suffix = '\0';
+			if(proto_sep > old_host)
+				suffix = proto_sep[-1];
+			if(suffix == '4') {
+				_addr.transport_proto = xstrndup(
+					old_host, proto_sep - old_host - 1);
+				_addr.address_space = xstrdup("ipv4");
+			} else if(suffix == '6') {
+				_addr.transport_proto = xstrndup(
+					old_host, proto_sep - old_host - 1);
+				_addr.address_space = xstrdup("ipv6");
+			} else if(suffix == '_') {
+				_addr.transport_proto = xstrndup(
+					old_host, proto_sep - old_host - 1);
+				_addr.address_space = NULL;
+			} else {
+				_addr.transport_proto = xstrndup(
+					old_host, proto_sep - old_host);
+				_addr.address_space = NULL;
+			}
+		}
+		_addr.host = xstrdup(proto_sep + 1);
+		free(old_host);
+	}
+
+	path_start = strchr(uhp_start, '/');
+	if (!path_start)
+		die("URL '%s': No '/' to start path", orig_addr);
+
+	_addr.path = xstrndup(path_start, addrlen - (path_start - addr));
+
+	if (*uhp_start == ':')
+		_addr.port = xstrndup(uhp_start + 1,
+			path_start - uhp_start - 1);
+	else
+		_addr.port = NULL;
+
+	if (!*_addr.host)
+		die("URL '%s': Empty hostname not allowed", orig_addr);
+
+	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 (!strcmp(_addr.protocol, "git") && _addr.uservid) {
+		die("git:// does not support unique server identitifier");
+	}
+
+	if (_addr.port) {
+		nondefault_port = 1;
+	} else if (!strcmp(_addr.protocol, "gits")) {
+		_addr.port = xstrndup("git", 3);
+	} else if (!strcmp(_addr.protocol, "git")) {
+		_addr.port = xstrndup("git", 3);
+	} else if (!strcmp(_addr.protocol, "tls")) {
+		_addr.port = xstrndup("gits", 4);
+	}
+
+	/* 8 is for host=[]:\0 */
+	vhost_len = 9 + strlen(_addr.host) + strlen(_addr.port);
+	_addr.vhost_header = xmalloc(vhost_len);
+
+	strcpy(_addr.vhost_header, "host=");
+	append_uniq_address(_addr.vhost_header, &_addr, !nondefault_port);
+
+	if (verbose) {
+		fprintf(stderr, "Protocol:           %s\n", _addr.protocol);
+		fprintf(stderr, "User:               %s\n", _addr.user ?
+			_addr.user :  "<not set>");
+		fprintf(stderr, "Host:               %s\n", _addr.host);
+		fprintf(stderr, "Unique server ID:   %s\n", _addr.uservid ?
+			_addr.uservid : "<any>");
+		fprintf(stderr, "Port:               %s\n", _addr.port);
+		fprintf(stderr, "Path:               %s\n", _addr.path);
+		fprintf(stderr, "Vhost header:       %s\n",
+			_addr.vhost_header);
+		fprintf(stderr, "Transport protocol: %s\n",
+			_addr.transport_proto);
+		fprintf(stderr, "Address space:      %s\n",
+			_addr.address_space ? _addr.address_space :
+			"<unspecified>");
+		fprintf(stderr, "\n");
+	}
+
+	return _addr;
+}
+
+#define MODE_ALLOW_EOF 0
+#define MODE_HANDSHAKE 1
+#define MAXFDS 128
+
+static void disconnect_outgoing(struct user *user)
+{
+	if(user_get_red_out(user))
+		user_set_red_io(user, -1, -1, -1);
+	else
+		user_set_red_io(user, -1, 1, -1);
+	user_send_red_in_eof(user);
+}
+
+static void traffic_loop(struct user *user, int mode)
+{
+	fd_set rfds;
+	fd_set wfds;
+	struct pollfd polled[MAXFDS];
+	int fdcount = 0;
+	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;
+	}
+	for(r = 0; r < bound || r <= socket_fd; r++) {
+		int added = 0;
+		polled[fdcount].fd = r;
+		polled[fdcount].events = 0;
+		polled[fdcount].revents = 0;
+		if(FD_ISSET(r, &rfds)) {
+			polled[fdcount].events |= (POLLIN | POLLHUP);
+			added = 1;
+		}
+		if(FD_ISSET(r, &wfds)) {
+			polled[fdcount].events |= (POLLOUT | POLLHUP);
+			added = 1;
+		}
+		if(r == socket_fd) {
+			polled[fdcount].events |= POLLHUP;
+			added = 1;
+		}
+		if(added)
+			fdcount++;
+	}
+
+	r = poll(polled, fdcount, -1);
+	if (r < 0 && errno != EINTR) {
+		die_errno("poll() failed");
+	} else if (r <= 0) {
+		FD_ZERO(&rfds);
+		FD_ZERO(&wfds);
+	} else {
+		FD_ZERO(&rfds);
+		FD_ZERO(&wfds);
+		for(r = 0; r < fdcount; r++) {
+			if(polled[r].revents & POLLHUP) {
+				/* Write stream hangup. Disconnect. */
+				if(polled[r].fd == 0)
+					FD_SET(0, &rfds);
+				if(polled[r].fd == 1)
+					FD_SET(1, &wfds);
+				if(polled[r].fd > 1)
+					disconnect_outgoing(user);
+			}
+			if(polled[r].revents & POLLIN)
+				FD_SET(polled[r].fd, &rfds);
+			if(polled[r].revents & POLLOUT)
+				FD_SET(polled[r].fd, &wfds);
+		}
+	}
+	in_handler = 1;
+	user_service(user, &rfds, &wfds);
+	in_handler = 0;
+	if (sigusr1_flag) {
+		sigusr1_flag = 0;
+		user_debug(debug_for, stderr);
+	}
+	failcode = user_get_failure(user);
+	if (failcode)
+		goto failed;
+	if (user_red_out_eofd(user) && !cbuffer_used(
+		user_get_red_out_force(user))) {
+		failcode = 1;
+		goto failed;
+	}
+
+	return;
+failed:
+	if (failcode > 0 && mode == MODE_ALLOW_EOF)
+		return;
+	else if (failcode > 0) {
+		error("Expected more data, got connection closed");
+		fprintf(stderr, "Debug dump:\n");
+		user_debug(user, stderr);
+		die("Expected more data, got connection closed");
+	} else if (failcode < 0) {
+		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;
+#endif
+	int kx[3];
+
+	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, "ssh-")) {
+			do_ssh_preauth(username + 4);
+			keypair_ok = 1;
+			goto skip_ksel;
+		}
+		if (!prefixcmp(username, "key-")) {
+			select_keypair(creds, username + 4, 1);
+			keypair_ok = 1;
+		} else
+			keypair_ok = (select_keypair(creds, username, 0)
+				>= 0);
+	}
+
+skip_ksel:
+	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
+	goto skip_force;
+no_srp:
+	kx[0] = GNUTLS_KX_DHE_DSS;
+	kx[1] = GNUTLS_KX_DHE_RSA;
+	kx[2] = 0;
+	s = gnutls_kx_set_priority(session, kx);
+	if (s < 0)
+		die("Can't force Diffie-Hellman: %s", gnutls_strerror(s));
+skip_force:
+	;
+}
+
+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);
+
+	if(hostname)
+		check_hostkey(session, hostname);
+}
+
+#define MAX_REQUEST 8192
+const char *hexes = "0123456789abcdef";
+
+static void do_request(const char *arg, struct parsed_addr *addr,
+	int suppress_ok, int no_repo)
+{
+	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;
+	int do_tls = 0;
+
+#ifdef SIGPIPE
+	signal(SIGPIPE, SIG_IGN);
+#endif
+
+	preconfigure_tls(addr->user);
+
+	fd = connect_host(addr->host, addr->port,
+		addr->transport_proto, addr->address_space);
+
+	/* Create dispatcher with no time limit. */
+	debug_for = dispatcher = user_create(fd, 65535);
+	if (!dispatcher)
+		die("Can't create connection context");
+	socket_fd = fd;
+	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")) {
+		if (verbose)
+			fprintf(stderr, "Configuring TLS...\n");
+		configure_tls(dispatcher, addr->uservid);
+		do_tls = 1;
+	} else {
+		if (verbose)
+			fprintf(stderr, "Requesting TLS...\n");
+		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 (s >= 0 && !strcmp(tmpbuf, "notsupp\n"))
+				die("Server does not support gits://");
+			if (user_red_out_eofd(dispatcher))
+				goto wait_eofd;
+			if (user_get_failure(dispatcher))
+				goto wait_failed;
+		}
+		if (verbose)
+			fprintf(stderr, "Server ready for TLS. Configuring TLS...\n");
+		configure_tls(dispatcher, addr->uservid);
+		do_tls = 1;
+	}
+
+	if (do_tls) {
+		if (verbose)
+			fprintf(stderr, "Waiting for TLS link to establish...\n");
+
+		while (!user_get_tls(dispatcher))
+			traffic_loop(dispatcher, MODE_HANDSHAKE);
+
+		if (verbose)
+			fprintf(stderr, "Secure link established.\n");
+	}
+
+	if (addr->user && !prefixcmp(addr->user, "ssh-")) {
+		if (verbose)
+			fprintf(stderr, "Sending SSH auth request...\n");
+		send_ssh_authentication(dispatcher, addr->user + 4);
+		while (cbuffer_used(inbuf))
+			traffic_loop(dispatcher, MODE_HANDSHAKE);
+		if (verbose)
+			fprintf(stderr, "Sent SSH auth request.\n");
+	}
+
+	if (!no_repo)
+		reqsize = strlen(arg) + strlen(addr->path) +  3 +
+			strlen(addr->vhost_header);
+	else
+		reqsize = strlen(arg);
+
+	if (reqsize > MAX_REQUEST)
+		die("Request too big to send");
+
+	memcpy(reqbuf + 4, arg, strlen(arg));
+	if (!no_repo) {
+		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];
+
+	if (verbose)
+		fprintf(stderr, "Sending request...\n");
+	cbuffer_write(inbuf, (unsigned char*)reqbuf, reqsize + 4);
+	if (verbose)
+		fprintf(stderr, "Request sent, waiting for reply...\n");
+	while (!cbuffer_used(outbuf) && !suppress_ok)
+		traffic_loop(dispatcher, MODE_HANDSHAKE);
+	if (verbose)
+		fprintf(stderr, "Server replied.\n");
+	/* Ok, remote end has replied. */
+	if(!suppress_ok)
+		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 (getenv("GITS_VERBOSE"))
+		verbose = 1;
+
+	if (argc < 3) {
+		die("Need two arguments");
+	}
+
+	signal(SIGUSR1, sigusr1_handler);
+
+	paddr = parse_address(argv[2]);
+
+	if (!prefixcmp(argv[1], "--service=")) {
+		do_request(argv[1] + 10, &paddr, 1, 0);
+		return 0;
+	}
+	if (!prefixcmp(argv[1], "--nourl-service=")) {
+		do_request(argv[1] + 16, &paddr, 1, 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, 0);
+			return 0;
+		} else
+			die("Unknown command %s", cmd);
+	}
+	return 0;
+}
diff --git a/git-over-tls/misc.c b/git-over-tls/misc.c
new file mode 100644
index 0000000..3d3e0b3
--- /dev/null
+++ b/git-over-tls/misc.c
@@ -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.
+ */
+#include "misc.h"
+#include <unistd.h>
+#include <errno.h>
+
+void force_close(int fd)
+{
+	while (close(fd) < 0 && errno != EBADF);
+}
diff --git a/git-over-tls/misc.h b/git-over-tls/misc.h
new file mode 100644
index 0000000..b482f2c
--- /dev/null
+++ b/git-over-tls/misc.h
@@ -0,0 +1,27 @@
+/*
+ * 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 _misc__h__included__
+#define _misc__h__included__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Forcibly close the file descriptor.
+ *
+ *Input:
+ *	fd		The file descriptor.
+ */
+void force_close(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/git-over-tls/mkcert.c b/git-over-tls/mkcert.c
new file mode 100644
index 0000000..b8eaacb
--- /dev/null
+++ b/git-over-tls/mkcert.c
@@ -0,0 +1,474 @@
+/*
+ * 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
+
+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 %lu "
+			"bytes).\n", (unsigned long)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 (!ensure_directory("$HOME") < 0)
+		die("Hey, you don't have a home directory!");
+
+	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, "$XDG_CONFIG_HOME/gits/keys/%s", name);
+	}
+	if (!strcmp(name, "")) {
+		fprintf(stderr, "Bad name\n");
+		goto retry_name;
+	}
+
+	fd = open_create_dirs(fbuffer, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	if (fd < 0) {
+		error("Can't open '%s': %s", 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..13b9f5e
--- /dev/null
+++ b/git-over-tls/srp_askpass.c
@@ -0,0 +1,110 @@
+/*
+ * 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 *prompt,
+	const char *prog)
+{
+	static char buffer[PROMPTBUF + 1];
+	static char cmdbuffer[CMDBUFSIZE + 1];
+	char *ans;
+	int escape = 0;
+	int idx;
+	int widx = 0;
+	FILE *out;
+
+	if (strchr(prompt, '\"'))
+		die("Can't prompt for names containing '\"'");
+
+	for (idx = 0; prog[idx]; idx++) {
+		if (!escape && prog[idx] == '%')
+			escape = 1;
+		else if (escape && prog[idx] == 'p') {
+			if (widx + strlen(prompt) + 2 >= CMDBUFSIZE)
+				die("Command line too long");
+			cmdbuffer[widx++] = '\"';
+			strcpy(cmdbuffer + widx, prompt);
+			widx += strlen(prompt);
+			cmdbuffer[widx++] = '\"';
+		} 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)) {
+		if (ferror(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_password(const char *prompt)
+{
+	if (getenv("GITS_ASKPASS"))
+		return get_password_via_external(prompt,
+			getenv("GITS_ASKPASS"));
+
+	return prompt_string(prompt, 1);
+}
+
+char *get_srp_password(const char *username)
+{
+	static char buffer[PROMPTBUF + 1];
+	int len;
+
+	len = snprintf(buffer, PROMPTBUF + 1, "Enter SRP password for %s",
+		username);
+	if (len < 0 || len > PROMPTBUF)
+		die("SRP Username is insanely long");
+
+	return get_password(buffer);
+}
+
+char *get_ssh_password(const char *keyname)
+{
+	static char buffer[PROMPTBUF + 1];
+	int len;
+
+	len = snprintf(buffer, PROMPTBUF + 1, "Enter passphrase to unlock %s",
+		keyname);
+	if (len < 0 || len > PROMPTBUF)
+		die("Key name is insanely long");
+
+	return get_password(buffer);
+}
diff --git a/git-over-tls/srp_askpass.h b/git-over-tls/srp_askpass.h
new file mode 100644
index 0000000..cb79d34
--- /dev/null
+++ b/git-over-tls/srp_askpass.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 _srp_askpass__h__included__
+#define _srp_askpass__h__included__
+
+/* Get password. Return is malloced */
+char *get_password(const char *prompt);
+char *get_srp_password(const char *username);
+char *get_ssh_password(const char *keyname);
+
+#endif
-- 
1.7.1.rc2.10.g714149

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]