Show changes to 26 files +3378 −0
git-over-tls/.gitignore, git-over-tls/base64.c, git-over-tls/base64.h, git-over-tls/certificate.c, git-over-tls/certificate.h, git-over-tls/connect.c, git-over-tls/connect.h, git-over-tls/genkeypair.c, git-over-tls/gensrpverifier.c, git-over-tls/getkeyid.c, git-over-tls/gits-send-special-command, git-over-tls/gits-send-special-command-nourl, git-over-tls/home.c, git-over-tls/home.h, git-over-tls/hostkey.c, git-over-tls/hostkey.h, git-over-tls/keypairs.c, git-over-tls/keypairs.h, git-over-tls/main.c, git-over-tls/misc.c, git-over-tls/misc.h, git-over-tls/mkcert.c, git-over-tls/prompt.c, git-over-tls/prompt.h, git-over-tls/srp_askpass.c, 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