Git development
 help / color / mirror / Atom feed
* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
From: Andreas Krey @ 2010-01-13 16:17 UTC (permalink / raw)
  To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
In-Reply-To: <20100113144745.GA7246@Knoppix>

On Wed, 13 Jan 2010 16:47:47 +0000, Ilari Liusvaara wrote:
...
> > It doesn't need to, really. stunnel sets the environment variable
> > SSL_CLIENT_DN with the distinguished name of the client certificate,
> > which can be used in the hook scripts ('update') on the server.
> 
> That would be useless. Data about authenticated client needs to fed to
> authorization decisions already before invoking git.

If you don't want public read access then you need to wedge a script
between stunnel and git itself that checks whether authentication is
present, yes.

> And besides: Gits:// uses certificates as keypairs,

My gripe with this is that I would expect gits: to be the same
as git: except that there is SSL underneath. git: does not have
authentication, so there should be none in gits: except what
SSL provides. (And the auth via unix domain sockets would be
usable for plain git: as well; there is no reason to encrypt
local traffic?)

(Is the unix auth via unix domain sockets part of GnuTLS?)

> which would make DN
> data absolutely useless because it is untrustworthy. And adding PKI
> is way too complicated.

That's another story. I think that it would be possible nowadays
to implement gits:// (in both ways) via core.gitproxy and a server-side
wrapper program (stunnel or else), but that has the disadvantage of
being unable to just provide a clone url without installing special
software besides git.

...
> The authentication support for smart-http seems pretty bad (making the
> old mistake of not binding authentications).

Mind to explain 'binding authentications'?

Andreas

^ permalink raw reply

* Filenames and prefixes in extended diffs
From: Andreas Gruenbacher @ 2010-01-13 16:13 UTC (permalink / raw)
  To: git

I'm having a problem filename prefixes in git's extended diffs for patches 
which rename or copy files: those patches include the old and new filenames in 
"rename from", "rename to", "copy from", and "copy to" headers, e.g.,

	$ git show -M
	diff --git a/f b/g
	similarity index 87%
	rename from f
	rename to g
	index f00c965..3bb459b 100644
	--- a/f
	+++ b/g
	@@ -8,3 +8,4 @@
	 8
	 9
	 10
	+11

Unlike the filenames in the "diff --git", "---", and "+++" headers, the 
"rename from", "rename to", "copy from", and "copy to" filenames do not 
include prefixes.

Now when applying a patch, GNU patch's -p option determines the number of 
pathname components to strip off from filenames.  This obviously can't work 
consistently for the prefixed and prefix-less headers.

Can git be changed to include prefixes in all filenames?

The only alternative I see is to ignore the filenames in the rename/copy 
headers and rely only on the "diff --git" line.  (The "---" and "+++" headers 
are not guaranteed to exist.)  What's worse, as already discussed here, the 
"diff --git" line uses space as a separator between filenames yet it doesn't 
quote spaces in filenames.  When being forced to ignore rename/copy headers, 
this defect would make things much worse.


Any ideas?


Thanks,
Andreas

^ permalink raw reply

* Re: [PATCH] grep: -L should show empty files
From: Sverre Rabbelier @ 2010-01-13 16:04 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Linus Torvalds, Miles Bader, Jeff King, Nguyen Thai Ngoc Duy
In-Reply-To: <7vpr5esdbp.fsf@alter.siamese.dyndns.org>

Heya,

On Wed, Jan 13, 2010 at 07:56, Junio C Hamano <gitster@pobox.com> wrote:
> It's Ok as the price we pay for producing correct result is to open those
> empty files, read them, and look for matches which we will never find ;-)

I'm not that familiar with the code, but wouldn't it be possible to
keep the early abort, but make it dependent on not using the '-L'
flag?

-- 
Cheers,

Sverre Rabbelier

^ permalink raw reply

* Re: [PATCH] git push --track
From: Rudolf Polzer @ 2010-01-13 15:55 UTC (permalink / raw)
  To: git
In-Reply-To: <20100113154310.GA7348@Knoppix>

On Wed, 13 Jan 2010 16:43:10 +0100, Ilari Liusvaara  
<ilari.liusvaara@elisanet.fi> wrote:

> On Wed, Jan 13, 2010 at 04:12:49PM +0100, Rudolf Polzer wrote:
>> Hi,
>>
>> I'd like a feature to automatically "transform" a non-tracking local
>> branch into a tracking branch on push. A patch to do that is
>> attached.
>
> The patches should be sent inline, together with commit messages
> (unless you are asked to resend as attachment because of whitespace
> mangling). Attached patches are very hard to comment on.
>
>> Are there any chances for this getting added to official git - or an
>> alternate convenient way convert a local to a tracking branch?
>
> This is missing sign-off. It can't be included without it.

Of course, but I assume the sign-off would not be by me, but by some of  
the git developers, and would depend on whether they actually want this  
feature.

> Also couple comments:
>
> - Some lines look way too long (~160 chars, should be max 80 unles
> it would linebreak error message).

Yes, also I got told that I used the wrong braces style... well, fixed  
that.

> - Should the tracking be set up even if only part of ref update suceeded
> (for those that succeeded), not requiring all to succeed?

Good point, but I simply see no clean way to set it up for the succeeded  
refs. Would be a nice idea for improvement of this.

> - Is --track the best name for this?

I am assuming this, because this is what git-checkout and git-branch use  
for the same thing.

As I am absolutely not sure if with Opera I can include the file as is, I  
also provided it on http://nopaste.linux-dev.org/?6248 this time.

Best regards,

Rudolf


 From 123598516c7d4e1f83591e8dae64e2c76dc87c90 Mon Sep 17 00:00:00 2001
From: Rudolf Polzer <divVerent@alientrap.org>
Date: Wed, 13 Jan 2010 16:42:04 +0100
Subject: [PATCH 1/2] Add a feature "git push --track" to automatically  
make the pushed branches tracking

---
  builtin-push.c |   33 +++++++++++++++++++++++++++++++++
  transport.h    |    1 +
  2 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/builtin-push.c b/builtin-push.c
index 28a26e7..e5b66a3 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -7,6 +7,7 @@
  #include "builtin.h"
  #include "remote.h"
  #include "transport.h"
+#include "branch.h"
  #include "parse-options.h"

  static const char * const push_usage[] = {
@@ -115,6 +116,36 @@ static int push_with_options(struct transport  
*transport, int flags)
  		fprintf(stderr, "Pushing to %s\n", transport->url);
  	err = transport_push(transport, refspec_nr, refspec, flags,
  			     &nonfastforward);
+	if (err == 0 && flags & TRANSPORT_PUSH_TRACK) {
+		struct ref *remote_refs =
+			transport->get_refs_list(transport, 1);
+		struct ref *local_refs = get_local_heads();
+		int match_flags = 0;
+		if (flags & TRANSPORT_PUSH_ALL)
+			match_flags |= MATCH_REFS_ALL;
+		if (flags & TRANSPORT_PUSH_MIRROR)
+			match_flags |= MATCH_REFS_MIRROR;
+		if(!(flags & TRANSPORT_PUSH_DRY_RUN))
+		if(!match_refs(local_refs, &remote_refs, refspec_nr, refspec,
+					match_flags)) {
+			struct ref *next = remote_refs;
+			while(next) {
+				if(next->peer_ref && *next->peer_ref->name &&
+						*next->name &&
+						next->peer_ref->new_sha1 &&
+						!is_null_sha1(next->peer_ref->new_sha1))
+				{
+					if (!prefixcmp(next->peer_ref->name,
+								"refs/heads/"))
+						install_branch_config(BRANCH_CONFIG_VERBOSE,
+								next->peer_ref->name + 11,
+								transport->remote->name,
+								next->name);
+				}
+				next = next->next;
+			}
+		}
+	}
  	if (err != 0)
  		error("failed to push some refs to '%s'", transport->url);

@@ -218,6 +249,8 @@ int cmd_push(int argc, const char **argv, const char  
*prefix)
  		OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
  		OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive  
pack program"),
  		OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack  
program"),
+		OPT_BIT('t', "track",  &flags, "set up tracking mode (see git-pull(1))",
+			TRANSPORT_PUSH_TRACK),
  		OPT_END()
  	};

diff --git a/transport.h b/transport.h
index 9e74406..8a9c776 100644
--- a/transport.h
+++ b/transport.h
@@ -74,6 +74,7 @@ struct transport {
  #define TRANSPORT_PUSH_VERBOSE 16
  #define TRANSPORT_PUSH_PORCELAIN 32
  #define TRANSPORT_PUSH_QUIET 64
+#define TRANSPORT_PUSH_TRACK 128

  /* Returns a transport suitable for the url */
  struct transport *transport_get(struct remote *, const char *);
-- 
1.6.3.3


 From bbdd185ac43fb789f35d0177697486457af87fd0 Mon Sep 17 00:00:00 2001
From: Rudolf Polzer <divVerent@alientrap.org>
Date: Wed, 13 Jan 2010 16:47:24 +0100
Subject: [PATCH 2/2] tracking into Docs

---
  Documentation/git-push.txt |    8 ++++++++
  1 files changed, 8 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index e3eb1e8..ebaa67b 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -82,6 +82,14 @@ nor in any Push line of the corresponding remotes  
file---see below).
  	if the configuration option `remote.<remote>.mirror` is
  	set.

+-t::
+--track::
+	When pushing, set up "upstream" configuration. See
+	"--track" in linkgit:git-branch[1] for details. All
+	refspecs that have a branch as source ref will be turned
+	into tracking branches if they are not already, and in any case
+	adjusted to track the given remote and ref on the remote side.
+
  -n::
  --dry-run::
  	Do everything except actually send the updates.
-- 
1.6.3.3

^ permalink raw reply related

* Re: [PATCH] git push --track
From: Ilari Liusvaara @ 2010-01-13 15:43 UTC (permalink / raw)
  To: Rudolf Polzer; +Cc: git
In-Reply-To: <op.u6g8jnixg402ra@nb-04>

On Wed, Jan 13, 2010 at 04:12:49PM +0100, Rudolf Polzer wrote:
> Hi,
> 
> I'd like a feature to automatically "transform" a non-tracking local
> branch into a tracking branch on push. A patch to do that is
> attached.

The patches should be sent inline, together with commit messages
(unless you are asked to resend as attachment because of whitespace
mangling). Attached patches are very hard to comment on.

> Are there any chances for this getting added to official git - or an
> alternate convenient way convert a local to a tracking branch?

This is missing sign-off. It can't be included without it.

Also couple comments:

- Some lines look way too long (~160 chars, should be max 80 unles
it would linebreak error message).
- Should the tracking be set up even if only part of ref update suceeded
(for those that succeeded), not requiring all to succeed?
- Is --track the best name for this?

-Ilari

^ permalink raw reply

* [PATCH] git push --track
From: Rudolf Polzer @ 2010-01-13 15:12 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 798 bytes --]

Hi,

I'd like a feature to automatically "transform" a non-tracking local  
branch into a tracking branch on push. A patch to do that is attached.

Usage:

git branch mybranch
git checkout mybranch
...
git push --track origin mybranch:mybranch

will not just perform the push, but also write a block

[branch "mybranch"]
         remote = origin
         merge = refs/heads/mybranch

to the git configuration so the branch becomes tracking.

This should be a simpler alternative to the otherwise usual procedure

git push origin mybranch:mybranch
git config branch.mybranch.remote origin
git config branch.mybranch.merge refs/heads/mybranch

Are there any chances for this getting added to official git - or an  
alternate convenient way convert a local to a tracking branch?

Best regards,

Rudolf

[-- Attachment #2: git-push-track.diff --]
[-- Type: application/octet-stream, Size: 2340 bytes --]

diff --git a/builtin-push.c b/builtin-push.c
index 28a26e7..8d68646 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -7,6 +7,7 @@
 #include "builtin.h"
 #include "remote.h"
 #include "transport.h"
+#include "branch.h"
 #include "parse-options.h"
 
 static const char * const push_usage[] = {
@@ -115,6 +116,33 @@ static int push_with_options(struct transport *transport, int flags)
 		fprintf(stderr, "Pushing to %s\n", transport->url);
 	err = transport_push(transport, refspec_nr, refspec, flags,
 			     &nonfastforward);
+	if (err == 0 && flags & TRANSPORT_PUSH_TRACK)
+	{
+		struct ref *remote_refs =
+			transport->get_refs_list(transport, 1);
+		struct ref *local_refs = get_local_heads();
+		int match_flags = 0;
+		if (flags & TRANSPORT_PUSH_ALL)
+			match_flags |= MATCH_REFS_ALL;
+		if (flags & TRANSPORT_PUSH_MIRROR)
+			match_flags |= MATCH_REFS_MIRROR;
+		if(!(flags & TRANSPORT_PUSH_DRY_RUN))
+		if(!match_refs(local_refs, &remote_refs, refspec_nr, refspec, match_flags))
+		{
+			struct ref *next = remote_refs;
+			while(next)
+			{
+				if(next->peer_ref && *next->peer_ref->name && *next->name && next->peer_ref->new_sha1 && !is_null_sha1(next->peer_ref->new_sha1))
+				{
+					if (!prefixcmp(next->peer_ref->name, "refs/heads/"))
+					{
+						install_branch_config(BRANCH_CONFIG_VERBOSE, next->peer_ref->name + 11, transport->remote->name, next->name);
+					}
+				}
+				next = next->next;
+			}
+		}
+	}
 	if (err != 0)
 		error("failed to push some refs to '%s'", transport->url);
 
@@ -218,6 +246,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
 		OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
 		OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+		OPT_BIT('t', "track",  &flags, "set up tracking mode (see git-pull(1))",
+			TRANSPORT_PUSH_TRACK),
 		OPT_END()
 	};
 
diff --git a/transport.h b/transport.h
index 9e74406..8a9c776 100644
--- a/transport.h
+++ b/transport.h
@@ -74,6 +74,7 @@ struct transport {
 #define TRANSPORT_PUSH_VERBOSE 16
 #define TRANSPORT_PUSH_PORCELAIN 32
 #define TRANSPORT_PUSH_QUIET 64
+#define TRANSPORT_PUSH_TRACK 128
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);

^ permalink raw reply related

* Re: [PATCH 1/2] grep: rip out support for external grep
From: Linus Torvalds @ 2010-01-13 15:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Miles Bader, Jeff King, Nguyen Thai Ngoc Duy
In-Reply-To: <7v4omqv6tx.fsf_-_@alter.siamese.dyndns.org>



On Tue, 12 Jan 2010, Junio C Hamano wrote:
> 
>  These nits out-of-way, we can now start doing this.
> 
>  Makefile        |   10 --
>  builtin-grep.c  |  305 +------------------------------------------------------
>  t/t7002-grep.sh |    6 +-
>  3 files changed, 8 insertions(+), 313 deletions(-)

Yay!

		Linus

^ permalink raw reply

* Re: remote to push to local branch: hung up unexpectedly
From: Michael S. Tsirkin @ 2010-01-13 15:12 UTC (permalink / raw)
  To: Tay Ray Chuan; +Cc: git
In-Reply-To: <be6fef0d1001130649i6a5f4f29j10800f2532d97796@mail.gmail.com>

On Wed, Jan 13, 2010 at 10:49:44PM +0800, Tay Ray Chuan wrote:
> Hi,
> 
> On Wed, Jan 13, 2010 at 10:28 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> > On Wed, Jan 13, 2010 at 10:15:38PM +0800, Tay Ray Chuan wrote:
> >> Hi,
> >>
> >> On Wed, Jan 13, 2010 at 9:08 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> >> > with url = /scm/qemu   (this is repo path)
> >>
> >> Are you working in a "normal" git setup with a .git folder and the
> >> files checked out? Or are you working with a --bare repo?
> >
> > It's a normal setup.
> 
> I used this script with v1.6.2.5, push was ok.

And so it was for me. Also if I clone the tree everything
is OK.  So it's specific to this tree.

>   #!/bin/bash
>   GITZ="/home/rctay/ext01/dev/git/git-1.6.2.5/git \
>   --exec-path=/home/rctay/ext01/dev/git/git-1.6.2.5/"
>   $GITZ --version
> 
>   mkdir foo
>   cd foo
>   $GITZ init
> 
>   echo hello > file1
>   $GITZ add file1
>   $GITZ commit -m "new file"
>   $GITZ checkout -b pci
> 
>   $GITZ config remote.anthony.url .
>   $GITZ config remote.anthony.push +refs/heads/pci:refs/heads/anthony
> 
>   $GITZ push anthony
> 
> -- 
> Cheers,
> Ray Chuan

^ permalink raw reply

* Re: git-svn doesn't fetch an empty directory with svn:externals
From: Erik Faye-Lund @ 2010-01-13 15:11 UTC (permalink / raw)
  To: Michel Jouvin; +Cc: git
In-Reply-To: <loom.20100113T124446-187@post.gmane.org>

On Wed, Jan 13, 2010 at 12:49 PM, Michel Jouvin <jouvin@lal.in2p3.fr> wrote:
> Hi,
>
> I'm running in a problem when trying to fetch a SVN repository branch that
> contains an empty directory with a SVN property svn:externals attached. This
> directory is missing in the Git repository/checkout. I was unable to find an
> option to have it added. I'd like to get it added to readd the externals using
> the trick described in http://kerneltrap.org/mailarchive/git/2007/5/1/245002.
> Without it, I have to recreate it in git, add it to .gitignore... which is
> painful.
>
> BTW, I didn't find any documentation on empty dirs handling by git-svn. They
> seems to be often removed which is not always desirable. Are they options
> related to this?
>

I don't think this is git-svn's fault, but simply that git doesn't
track directories (just files). I guess git-svn could create some sort
of place-holder files in empty folders, but currently it doesn't
AFAIK.

-- 
Erik "kusma" Faye-Lund

^ permalink raw reply

* Re: [RFH] Git and filesystem ACLs: problem with 'git gc'
From: Matthieu Moy @ 2010-01-13 14:56 UTC (permalink / raw)
  To: git
In-Reply-To: <vpqy6k38lzd.fsf@bauges.imag.fr>

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:

> I investigated a bit, and the problem seems to come from mkstemp,
> which is used by write_pack_file to create the temporary file: files
> created by mkstemp get an ACL umask of ---.
>
> Is it really a good idea to use mkstemp? We're inside
> .git/object/pack, for which the user is supposed to have already set
> correct permissions, so shouldn't we just create a random file name
> and then use a plain open(...) to create the file, leaving the umask
> do its job to control the permissions?

Digging a bit further, I noticed that _object_ creation was doing a

  set_shared_perm(filename, (S_IFREG|0444))

thus ignoring the umask, and setting r--r--r-- for all objects, while
_pack_ creation does roughly (in write_pack_file()) :

  mode_t mode = umask(0);
  mode = 0444 & ~mode;
  adjust_perm(pack_tmp_name, mode)

Thus setting the permissions to r--X--X-- where X is defined by the
umask. Is there any reason for this difference? I'd say we can rely on
the containing directory's permissions, and do for pack what Git
already does for objects.

[ On a side note, I don't understand what the S_IFREG is doing in the
call to set_shared_perm. It's passed to chmod, while S_IFREG is only
documented in the manpage for stat() ... ]

Thanks,

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

^ permalink raw reply

* Re: [PATCH/RFC] filter-branch: Fix to allow replacing submodules with another content
From: Michal Sojka @ 2010-01-13 14:56 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, Lars Hjemli
In-Reply-To: <alpine.DEB.1.00.1001111901380.4985@pacific.mpi-cbg.de>

On Monday 11 of January 2010 19:02:06 Johannes Schindelin wrote:
> Hi,
> 
> On Mon, 11 Jan 2010, Michal Sojka wrote:
> > When git filter-branch is used to replace a submodule with another
> > content, it always fails on the first commit. Consider a repository with
> > directory submodule containing a submodule. If I want to remove the
> > submodule and replace it with a file, the following command fails.
> >
> > git filter-branch --tree-filter 'rm -rf submodule &&
> > 				 git rm -q submodule &&
> > 				 mkdir submodule &&
> > 				 touch submodule/file'
> >
> > The error message is:
> > error: submodule: is a directory - add files inside instead
> >
> > The reason is that git diff-index, which generates a part of the list of
> > files to update-index, emits also the removed submodule even if it was
> > replaced by a real directory.
> >
> > Adding --ignored-submodules solves the problem for me and
> > tests in t7003-filter-branch.sh passes correctly.
> 
> Have you tested replacing one revision of a submodule with another?

Hi,

no, I didn't try it. I wanted to test it now but it seems to me that
it cannot work even without my patch. I may be trying wrong
--tree-filter. If anybody has a better idea, let me know.

I suppose that in order to change the revision of the submodule within
the tree filter, I need to checkout the original revision first. Then
I could use e.g. git reset HEAD^ to change it. Unfortunately even
checkout of the submodule fails:

$ git filter-branch --tree-filter "git submodule update --init" HEAD
Clone of '/tmp/submod' into submodule path 'submod' failed
tree filter failed: git submodule update --init

This is because filter-branch sets GIT_WORKING_TREE to "." which
causes clone to fail.

Replacing a revision of a submodule can be done only by manipulating
index, but for this case you would use index-filter rather than
tree-filter. Right?

I have created a few tests for testing filter-branch with submodules.
See the patch bellow.

Michal

>From 9aa38185d795061a2f00204d181244f906280b5a Mon Sep 17 00:00:00 2001
From: Michal Sojka <sojkam1@fel.cvut.cz>
Date: Wed, 13 Jan 2010 15:15:28 +0100
Subject: [PATCH] filter-branch: Add tests for submodules


Signed-off-by: Michal Sojka <sojkam1@fel.cvut.cz>
---
 t/t7003-filter-branch.sh |   26 ++++++++++++++++++++++++++
 1 files changed, 26 insertions(+), 0 deletions(-)

diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 9503875..daebd17 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -306,4 +306,30 @@ test_expect_success '--remap-to-ancestor with filename filters' '
 	test $orig_invariant = $(git rev-parse invariant)
 '
 
+test_expect_success 'setup submodule' '
+	rm -rf * .*
+	git init &&
+	test_commit file &&
+	mkdir submod &&
+	submodurl="$PWD/submod"
+	( cd submod &&
+	  git init &&
+	  test_commit file-in-submod ) &&
+	git submodule add "$submodurl"
+	git commit -m "added submodule" &&
+	test_commit add-file &&
+	( cd submod && test_commit add-in-submodule ) &&
+	git add submod &&
+	git commit -m "changed submodule"
+'
+
+test_expect_failure 'rewrite submodule with another content' '
+	git filter-branch --tree-filter "test -d submod && {
+					 rm -rf submod &&
+					 git rm -rf --quiet submod &&
+					 mkdir submod &&
+					 : > submod/file
+					 } || :"
+'
+
 test_done
-- 
1.6.6

^ permalink raw reply related

* Re: remote to push to local branch: hung up unexpectedly
From: Tay Ray Chuan @ 2010-01-13 14:49 UTC (permalink / raw)
  To: Michael S. Tsirkin; +Cc: git
In-Reply-To: <20100113142800.GA13901@redhat.com>

Hi,

On Wed, Jan 13, 2010 at 10:28 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> On Wed, Jan 13, 2010 at 10:15:38PM +0800, Tay Ray Chuan wrote:
>> Hi,
>>
>> On Wed, Jan 13, 2010 at 9:08 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
>> > with url = /scm/qemu   (this is repo path)
>>
>> Are you working in a "normal" git setup with a .git folder and the
>> files checked out? Or are you working with a --bare repo?
>
> It's a normal setup.

I used this script with v1.6.2.5, push was ok.

  #!/bin/bash
  GITZ="/home/rctay/ext01/dev/git/git-1.6.2.5/git \
  --exec-path=/home/rctay/ext01/dev/git/git-1.6.2.5/"
  $GITZ --version

  mkdir foo
  cd foo
  $GITZ init

  echo hello > file1
  $GITZ add file1
  $GITZ commit -m "new file"
  $GITZ checkout -b pci

  $GITZ config remote.anthony.url .
  $GITZ config remote.anthony.push +refs/heads/pci:refs/heads/anthony

  $GITZ push anthony

-- 
Cheers,
Ray Chuan

^ permalink raw reply

* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
From: Ilari Liusvaara @ 2010-01-13 14:47 UTC (permalink / raw)
  To: Andreas Krey; +Cc: Nguyen Thai Ngoc Duy, git
In-Reply-To: <20100113141218.GA17687@inner.home.ulmdo.de>

On Wed, Jan 13, 2010 at 03:12:18PM +0100, Andreas Krey wrote:
> On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:
> ...
> > And one would need custom daemon anyway even if one used stunnel. 
> > git-daemon just can't deal with authentication data.
> 
> It doesn't need to, really. stunnel sets the environment variable
> SSL_CLIENT_DN with the distinguished name of the client certificate,
> which can be used in the hook scripts ('update') on the server.

That would be useless. Data about authenticated client needs to fed to
authorization decisions already before invoking git.

And besides: Gits:// uses certificates as keypairs, which would make DN
data absolutely useless because it is untrustworthy. And adding PKI
is way too complicated.

> (I looked into that stuff once, but with the advent of smart-http(s)
> I pretty much lost any interest to try implementing gits:// via
> openssl here, as it isn't yet an actual itch.)

The authentication support for smart-http seems pretty bad (making the
old mistake of not binding authentications). Of course, the same tricks
as gits:// uses would work with https:// (its all TLS-level stuff), but
no server or client does that.

-Ilari

^ permalink raw reply

* Re: remote to push to local branch: hung up unexpectedly
From: Michael S. Tsirkin @ 2010-01-13 14:28 UTC (permalink / raw)
  To: Tay Ray Chuan; +Cc: git
In-Reply-To: <be6fef0d1001130615k17855680s57952498260ad09d@mail.gmail.com>

On Wed, Jan 13, 2010 at 10:15:38PM +0800, Tay Ray Chuan wrote:
> Hi,
> 
> On Wed, Jan 13, 2010 at 9:08 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> > with url = /scm/qemu   (this is repo path)
> 
> Are you working in a "normal" git setup with a .git folder and the
> files checked out? Or are you working with a --bare repo?

It's a normal setup.

> -- 
> Cheers,
> Ray Chuan

^ permalink raw reply

* Re: remote to push to local branch: hung up unexpectedly
From: Tay Ray Chuan @ 2010-01-13 14:15 UTC (permalink / raw)
  To: Michael S. Tsirkin; +Cc: git
In-Reply-To: <20100113130843.GA13545@redhat.com>

Hi,

On Wed, Jan 13, 2010 at 9:08 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> with url = /scm/qemu   (this is repo path)

Are you working in a "normal" git setup with a .git folder and the
files checked out? Or are you working with a --bare repo?

-- 
Cheers,
Ray Chuan

^ permalink raw reply

* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
From: Andreas Krey @ 2010-01-13 14:12 UTC (permalink / raw)
  To: Ilari Liusvaara; +Cc: Nguyen Thai Ngoc Duy, git
In-Reply-To: <20100113135753.GA7095@Knoppix>

On Wed, 13 Jan 2010 15:57:53 +0000, Ilari Liusvaara wrote:
...
> And one would need custom daemon anyway even if one used stunnel. 
> git-daemon just can't deal with authentication data.

It doesn't need to, really. stunnel sets the environment variable
SSL_CLIENT_DN with the distinguished name of the client certificate,
which can be used in the hook scripts ('update') on the server.

(I looked into that stuff once, but with the advent of smart-http(s)
I pretty much lost any interest to try implementing gits:// via
openssl here, as it isn't yet an actual itch.)

Andreas

^ permalink raw reply

* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
From: Ilari Liusvaara @ 2010-01-13 13:57 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git
In-Reply-To: <fcaeb9bf1001130539p2971caavd101d46de9269769@mail.gmail.com>

On Wed, Jan 13, 2010 at 08:39:12PM +0700, Nguyen Thai Ngoc Duy wrote:
>
> Can we rely on an external program, like stunnel, to do the job instead?

No. The way authentication is done is very unusual. I don't think stunnel (or
anything else) can deal with such modes. And the reason authentications are
done like they are done in order to minimize points of failure (getting
really annoyed at failure modes sshd introduced was one big reason for 
writing this).

I _definitely_ do not want to mess with X.509. And its not just about me
messing with it, it is also about pushing it to users.

And one would need custom daemon anyway even if one used stunnel. 
git-daemon just can't deal with authentication data.

-Ilari

^ permalink raw reply

* Re: [RFC 0/2] Git-over-TLS (gits://) client side support
From: Nguyen Thai Ngoc Duy @ 2010-01-13 13:39 UTC (permalink / raw)
  To: Ilari Liusvaara; +Cc: git
In-Reply-To: <1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi>

On 1/13/10, Ilari Liusvaara <ilari.liusvaara@elisanet.fi> wrote:
> This is client-side support for Git-over-TLS (gits://). gits:// is
>  version of git:// protocol layered on top of TLS (Transport Layer
>  Security). If using TLS, it is autenticated transport supporing
>  fetching, pushing and remote archive (plus special commands that
>  have server-dependent meaning).
>
>  Needs GnuTLS, and adds new make option NO_GNUTLS that disables builing
>  this code.
>
>  Supported underlying stream transports include TCP/IP, TCP/IPv6 and
>  Unix domain sockets (including Linux abstract namespace).
>
>  Supported authentication mechanisms include passwords, keypairs and on
>  some platforms Unix authentication if using unix domain sockets. Server
>  is authenticated using keypair (hostkey).
>
>  The patch is split into two parts because it would be otherwise be
>  too large for this list. Included are all the needed client side
>  utilities (some of them run gpg internally).

Can we rely on an external program, like stunnel, to do the job instead?
-- 
Duy

^ permalink raw reply

* Re: [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of  2)
From: Alex Riesen @ 2010-01-13 13:25 UTC (permalink / raw)
  To: Ilari Liusvaara; +Cc: git
In-Reply-To: <1263388786-6880-3-git-send-email-ilari.liusvaara@elisanet.fi>

On Wed, Jan 13, 2010 at 14:19, Ilari Liusvaara
<ilari.liusvaara@elisanet.fi> wrote:
> +char *copy_alloc(const char *str, size_t len)
> +{
> +       char *copy;
> +
> +       copy = xmalloc(len + 1);
> +       copy[len] = 0;
> +       strncpy(copy, str, len);
> +       return copy;
> +}

Some know this code as strndup(3):

  http://linux.die.net/man/3/strndup

^ permalink raw reply

* [RFC 2/2] Git-over-TLS (gits://) client side support (part 2 of 2)
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
  To: git
In-Reply-To: <1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi>

Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
 git-over-tls/main.c                       |  460 ++++++++++
 git-over-tls/{home.h => misc.c}           |   12 +-
 git-over-tls/{keypairs.h => misc.h}       |   21 +-
 git-over-tls/mkcert.c                     |  507 +++++++++++
 git-over-tls/prompt.c                     |  100 +++
 git-over-tls/prompt.h                     |   18 +
 git-over-tls/srp_askpass.c                |   90 ++
 git-over-tls/{hostkey.h => srp_askpass.h} |    9 +-
 git-over-tls/user.c                       | 1384 +++++++++++++++++++++++++++++
 git-over-tls/user.h                       |  357 ++++++++
 10 files changed, 2943 insertions(+), 15 deletions(-)
 create mode 100644 git-over-tls/main.c
 copy git-over-tls/{home.h => misc.c} (64%)
 copy git-over-tls/{keypairs.h => misc.h} (50%)
 create mode 100644 git-over-tls/mkcert.c
 create mode 100644 git-over-tls/prompt.c
 create mode 100644 git-over-tls/prompt.h
 create mode 100644 git-over-tls/srp_askpass.c
 copy git-over-tls/{hostkey.h => srp_askpass.h} (59%)
 create mode 100644 git-over-tls/user.c
 create mode 100644 git-over-tls/user.h

diff --git a/git-over-tls/main.c b/git-over-tls/main.c
new file mode 100644
index 0000000..a3c8f51
--- /dev/null
+++ b/git-over-tls/main.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "user.h"
+#include "srp_askpass.h"
+#include "keypairs.h"
+#include "hostkey.h"
+#include "connect.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <gnutls/gnutls.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+struct parsed_addr
+{
+	char *protocol;		/* Protocol part */
+	char *user;		/* User part, NULL if no user */
+	char *host;		/* Hostname */
+	char *uhost;		/* Unique host + port */
+	char *port;		/* Port as string, NULL if no port */
+	char *path;		/* Path part. */
+	char *vhost_header;	/* vhost header to send */
+	unsigned short _port;	/* Port as numeric. */
+};
+
+char *copy_alloc(const char *str, size_t len)
+{
+	char *copy;
+
+	copy = xmalloc(len + 1);
+	copy[len] = 0;
+	strncpy(copy, str, len);
+	return copy;
+}
+
+void append_uniq_address(char* buffer, struct parsed_addr* _addr)
+{
+	if (strchr(_addr->host, ':'))
+		strcat(buffer, "[");
+	strcat(buffer, _addr->host);
+	if (strchr(_addr->host, ':'))
+		strcat(buffer, "]");
+	if (_addr->port) {
+		strcat(buffer, ":");
+		strcat(buffer, _addr->port);
+	}
+}
+
+struct parsed_addr parse_address(const char *addr)
+{
+	struct parsed_addr _addr;
+	const char *proto_end;
+	const char *path_start;
+	const char *uhp_start;
+	const char *uhp_delim;
+	const char *orig_addr = addr;
+	size_t addrlen;
+
+	addrlen = strlen(addr);
+
+	proto_end = strchr(addr, ':');
+	if (!proto_end)
+		goto bad;
+
+	_addr.protocol = copy_alloc(addr, proto_end - addr);
+	if (strncmp(proto_end, "://", 3))
+		goto bad;
+
+	uhp_start = proto_end + 3;
+
+	/* Figure out the user if any. */
+	uhp_delim = strpbrk(uhp_start, "@[:/");
+
+	if (*uhp_delim == '@') {
+		_addr.user = copy_alloc(uhp_start,
+			uhp_delim - uhp_start);
+		uhp_start = uhp_delim + 1;
+	} else {
+		_addr.user = NULL;
+	}
+
+	/* Figure out host. */
+	if (*uhp_start == '[') {
+		uhp_delim = strpbrk(uhp_start, "]");
+		if (uhp_delim) {
+			_addr.host = copy_alloc(uhp_start + 1,
+				uhp_delim - uhp_start - 1);
+			if (uhp_delim[1] != ':' && uhp_delim[1] != '/')
+				goto bad;
+			uhp_start = uhp_delim + 1;
+		} else
+			goto bad;
+	} else {
+		uhp_delim = strpbrk(uhp_start, "[:/");
+		if (*uhp_delim == '[')
+			goto bad;
+		_addr.host = copy_alloc(uhp_start, uhp_delim - uhp_start);
+		uhp_start = uhp_delim;
+	}
+
+	path_start = strchr(uhp_start, '/');
+	if (!path_start)
+		goto bad;
+
+	_addr.path = copy_alloc(path_start, addrlen - (path_start - addr));
+
+	if (*uhp_start == ':')
+		_addr.port = copy_alloc(uhp_start + 1,
+			path_start - uhp_start - 1);
+	else
+		_addr.port = NULL;
+
+	if (!*_addr.host)
+		goto bad;
+
+	if (strcmp(_addr.protocol, "gits") && strcmp(_addr.protocol, "tls") &&
+		strcmp(_addr.protocol, "git")) {
+		die("Unknown protocol %s://", _addr.protocol);
+	}
+
+	if (!strcmp(_addr.protocol, "git") && _addr.user) {
+		die("git:// does not support users");
+	}
+
+	if (_addr.port) {
+		char *end;
+		unsigned long x;
+		x = strtoul(_addr.port, &end, 10);
+		if (*end)
+			goto bad;
+		if (x < 1 || x > 65535)
+			goto bad;
+		_addr._port = (unsigned short)x;
+	} else if (!strcmp(_addr.protocol, "gits")) {
+		_addr._port = 9418;
+	} else if (!strcmp(_addr.protocol, "git")) {
+		_addr._port = 9418;
+	} else if (!strcmp(_addr.protocol, "tls")) {
+		die("tls:// needs port specification");
+	}
+
+	if (_addr.port) {
+		/* 9 is for host=[]:\0 */
+		size_t vhost_len = 7 + strlen(_addr.host) +
+			strlen(_addr.port);
+		_addr.vhost_header = xmalloc(vhost_len);
+		_addr.uhost = xmalloc(vhost_len);
+	} else {
+		/* 8 is for host=[]\0 */
+		size_t vhost_len = 6 + strlen(_addr.host);
+		_addr.vhost_header = xmalloc(vhost_len);
+		_addr.uhost = xmalloc(vhost_len);
+	}
+
+	strcpy(_addr.vhost_header, "host=");
+	append_uniq_address(_addr.vhost_header, &_addr);
+
+	strcpy(_addr.uhost, "");
+	append_uniq_address(_addr.uhost, &_addr);
+
+	return _addr;
+bad:
+	die("Bad URL \"%s\"", orig_addr);
+	/* Can't come here. */
+	return _addr;
+}
+
+#define MODE_ALLOW_EOF 0
+#define MODE_HANDSHAKE 1
+
+static void traffic_loop(struct user *user, int mode)
+{
+	fd_set rfds;
+	fd_set wfds;
+	int failcode = 0;
+	struct timeval deadline;
+	int bound = 0;
+	int r;
+	FD_ZERO(&rfds);
+	FD_ZERO(&wfds);
+	user_add_to_sets(user, &bound, &rfds, &wfds, &deadline);
+	if (bound == 0) {
+		failcode = user_get_failure(user);
+		if (failcode)
+			goto failed;
+		return;
+	}
+	r = select(bound, &rfds, &wfds, NULL, NULL);
+	if (r < 0 && errno != EINTR) {
+		die_errno("select() failed");
+	} else if (r < 0) {
+		FD_ZERO(&rfds);
+		FD_ZERO(&wfds);
+	}
+	user_service(user, &rfds, &wfds);
+	failcode = user_get_failure(user);
+	if (failcode)
+		goto failed;
+	return;
+failed:
+	if (failcode > 0 && mode == MODE_ALLOW_EOF)
+		return;
+	else if (failcode > 0) {
+		die("Expected more data, got connection closed");
+	} else {
+		const char *major;
+		const char *minor;
+
+		major = user_explain_failure(failcode);
+		minor = user_get_error(user);
+
+		if (minor)
+			die("Connection lost: %s (%s)", major, minor);
+		else
+			die("Connection lost: %s", major);
+	}
+}
+
+static int select_keypair(gnutls_certificate_credentials_t creds,
+	const char *username, int must_succeed)
+{
+	int ret;
+	ret = select_keypair_int(creds, username);
+	if (ret < 0 && must_succeed)
+		die("No keypair identity %s found", username);
+	return ret;
+}
+
+static gnutls_session_t session;
+
+static void preconfigure_tls(const char *username)
+{
+	int s;
+	gnutls_certificate_credentials_t creds;
+	int keypair_ok = 0;
+#ifndef DISABLE_SRP
+	const char *srp_password;
+	gnutls_srp_client_credentials_t srp_cred;
+	int kx[3];
+#endif
+
+	s = gnutls_global_init();
+	if (s < 0)
+		die("Can't initialize GnuTLS: %s", gnutls_strerror(s));
+
+	s = gnutls_certificate_allocate_credentials(&creds);
+	if (s < 0)
+		die("Can't allocate cert creds: %s", gnutls_strerror(s));
+
+	s = gnutls_init(&session, GNUTLS_CLIENT);
+	if (s < 0)
+		die("Can't allocate session: %s", gnutls_strerror(s));
+
+#ifndef DISABLE_SRP
+	s = gnutls_priority_set_direct (session, "NORMAL:+SRP-DSS:+SRP-RSA",
+		NULL);
+#else
+	s = gnutls_priority_set_direct (session, "NORMAL", NULL);
+#endif
+	if (s < 0)
+		die("Can't set priority: %s", gnutls_strerror(s));
+
+	if (username) {
+		if (!prefixcmp(username, "key-")) {
+			select_keypair(creds, username + 4, 1);
+			keypair_ok = 1;
+		} else
+			keypair_ok = (select_keypair(creds, username, 0)
+				>= 0);
+	}
+
+	s = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, creds);
+	if (s < 0)
+		die("Can't set creds: %s", gnutls_strerror(s));
+
+	if (keypair_ok)
+		goto no_srp;
+#ifndef DISABLE_SRP
+	if (username && !prefixcmp(username, "srp-"))
+		username = username + 4;
+	if (!username || !*username)
+		goto no_srp;
+
+	s = gnutls_srp_allocate_client_credentials(&srp_cred);
+	if (s < 0)
+		die("Can't allocate SRP creds: %s", gnutls_strerror(s));
+
+	s = 0;
+	srp_password = get_srp_password(username);
+	s = gnutls_srp_set_client_credentials(srp_cred, username, srp_password);
+	if (s < 0)
+		die("Can't set SRP creds: %s", gnutls_strerror(s));
+
+	s = gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred);
+	if (s < 0)
+		die("Can't use SRP creds: %s", gnutls_strerror(s));
+
+	/* GnuTLS doesn't seem to like to use SRP. Force it. */
+	kx[0] = GNUTLS_KX_SRP_DSS;
+	kx[1] = GNUTLS_KX_SRP_RSA;
+	kx[2] = 0;
+	s = gnutls_kx_set_priority(session, kx);
+	if (s < 0)
+		die("Can't force SRP: %s", gnutls_strerror(s));
+#endif
+no_srp:
+	;
+}
+
+static void configure_tls(struct user *user, const char *hostname)
+{
+	user_configure_tls(user, session);
+
+	/* Wait for TLS connection to establish. */
+	while (!user_get_tls(user))
+		traffic_loop(user, MODE_HANDSHAKE);
+
+	check_hostkey(session, hostname);
+}
+
+#define MAX_REQUEST 8192
+const char *hexes = "0123456789abcdef";
+
+static void do_request(const char *arg, struct parsed_addr *addr,
+	int supress_ok)
+{
+	int fd;
+	struct user *dispatcher;
+	struct cbuffer *inbuf;
+	struct cbuffer *outbuf;
+	const char *major;
+	const char *minor;
+	char reqbuf[MAX_REQUEST + 4];
+	size_t reqsize;
+
+	preconfigure_tls(addr->user);
+
+	fd = connect_host(addr->host, addr->_port);
+
+	/* Create dispatcher with no time limit. */
+	dispatcher = user_create(fd, 65535);
+	if (!dispatcher)
+		die("Can't create connection context");
+	user_clear_deadline(dispatcher);
+
+	inbuf = user_get_red_in(dispatcher);
+	outbuf = user_get_red_out(dispatcher);
+	if (!strcmp(addr->protocol, "git")) {
+		; /* Not protected. */
+	} else if (!strcmp(addr->protocol, "tls")) {
+		configure_tls(dispatcher, addr->uhost);
+	} else {
+		cbuffer_write(inbuf, (unsigned char*)"000cstarttls", 12);
+		while (1) {
+			char tmpbuf[9];
+			int s;
+			traffic_loop(dispatcher, MODE_HANDSHAKE);
+			s = cbuffer_peek(outbuf, (unsigned char*)tmpbuf, 8);
+			tmpbuf[8] = '\0';
+			if (s >= 0 && !strcmp(tmpbuf, "proceed\n"))
+				break;
+			if (user_red_out_eofd(dispatcher))
+				goto wait_eofd;
+			if (user_get_failure(dispatcher))
+				goto wait_failed;
+		}
+		configure_tls(dispatcher, addr->uhost);
+	}
+
+	reqsize = strlen(arg) + strlen(addr->path) +  3 +
+		strlen(addr->vhost_header);
+
+	if (reqsize > MAX_REQUEST)
+		die("Request too big to send");
+
+	memcpy(reqbuf + 4, arg, strlen(arg));
+	reqbuf[strlen(arg) + 4] = ' ';
+	memcpy(reqbuf + strlen(arg) + 5, addr->path, strlen(addr->path) + 1);
+	memcpy(reqbuf + strlen(arg) + 6 + strlen(addr->path),
+		addr->vhost_header, strlen(addr->vhost_header) + 1);
+
+	reqbuf[0] = hexes[((reqsize + 4) >> 12) & 0xF];
+	reqbuf[1] = hexes[((reqsize + 4) >> 8) & 0xF];
+	reqbuf[2] = hexes[((reqsize + 4) >> 4) & 0xF];
+	reqbuf[3] = hexes[(reqsize + 4) & 0xF];
+
+	cbuffer_write(inbuf, (unsigned char*)reqbuf, reqsize + 4);
+	while (cbuffer_used(outbuf))
+		traffic_loop(dispatcher, MODE_HANDSHAKE);
+	/* Ok, remote end has replied. */
+	printf("\n");
+	fflush(stdout);
+	user_set_red_io(dispatcher, 0, 1, -1);
+
+	while (!user_get_failure(dispatcher))
+		traffic_loop(dispatcher, MODE_ALLOW_EOF);
+	exit(0);
+
+wait_failed:
+	major = user_explain_failure(user_get_failure(dispatcher));
+	minor = user_get_error(dispatcher);
+
+	if (minor)
+		die("Connection lost: %s (%s)", major, minor);
+	else
+		die("Connection lost: %s", major);
+	exit(128);
+wait_eofd:
+	die("Expected response to starttls, server closed connection.");
+	exit(128);
+}
+
+int main(int argc, char **argv)
+{
+	struct parsed_addr paddr;
+	char buffer[8192];
+
+	if (argc < 3) {
+		die("Need two arguments");
+	}
+
+	paddr = parse_address(argv[2]);
+
+	if (!prefixcmp(argv[1], "--service=")) {
+		do_request(argv[1] + 10, &paddr, 1);
+		return 0;
+	}
+
+	while (1) {
+		char *cmd;
+
+		cmd = fgets(buffer, 8190, stdin);
+		if (cmd[strlen(cmd) - 1] == '\n')
+			cmd[strlen(cmd) - 1] = '\0';
+
+		if (!strcmp(cmd, "capabilities")) {
+			printf("*connect\n\n");
+			fflush(stdout);
+		} else if (!*cmd) {
+			exit(0);
+		} else if (!prefixcmp(cmd, "connect ")) {
+			do_request(cmd + 8, &paddr, 0);
+			return 0;
+		} else
+			die("Unknown command %s", cmd);
+	}
+	return 0;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/misc.c
similarity index 64%
copy from git-over-tls/home.h
copy to git-over-tls/misc.c
index 133ee78..3d3e0b3 100644
--- a/git-over-tls/home.h
+++ b/git-over-tls/misc.c
@@ -5,9 +5,11 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
-#ifndef _home__h__included__
-#define _home__h__included__
+#include "misc.h"
+#include <unistd.h>
+#include <errno.h>
 
-const char *get_home();
-
-#endif
+void force_close(int fd)
+{
+	while (close(fd) < 0 && errno != EBADF);
+}
diff --git a/git-over-tls/keypairs.h b/git-over-tls/misc.h
similarity index 50%
copy from git-over-tls/keypairs.h
copy to git-over-tls/misc.h
index 11f4ef7..140341f 100644
--- a/git-over-tls/keypairs.h
+++ b/git-over-tls/misc.h
@@ -5,12 +5,23 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
-#ifndef _keypairs__h__included__
-#define _keypairs__h__included__
+#ifndef _misc__h__included__
+#define _misc__h__included__
 
-#include <gnutls/openpgp.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Forcibly close the file descriptor.
+ *
+ * Input
+ *	fd		The file descriptor.
+ */
+void force_close(int fd);
 
-int select_keypair_int(gnutls_certificate_credentials_t creds,
-	const char *username);
+#ifdef __cplusplus
+}
+#endif
 
 #endif
diff --git a/git-over-tls/mkcert.c b/git-over-tls/mkcert.c
new file mode 100644
index 0000000..597f942
--- /dev/null
+++ b/git-over-tls/mkcert.c
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "cbuffer.h"
+#include "home.h"
+#include "prompt.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/stat.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#include "run-command.h"
+#endif
+
+#define CERT_MAX 65536
+#define BUFSIZE 8192
+
+void ensure_leading_directories()
+{
+	struct stat s;
+	char fsobj[BUFSIZE];
+	int r;
+
+	sprintf(fsobj, "%s/.gits", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits");
+	else if (r == 0 && !S_ISDIR(s.st_mode))
+		die("$HOME/.gits exists but is not a directory");
+	else if (r < 0) {
+		/* Need to create it. */
+		if (mkdir(fsobj, 0700) < 0)
+			die_errno("Create $HOME/.gits failed");
+	}
+	/* Otherwise, r == 0 && S_ISDIR(s), which is OK. */
+	sprintf(fsobj, "%s/.gits/keys", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits/keys");
+	else if (r == 0 && !S_ISDIR(s.st_mode))
+		die("$HOME/.gits/keys exists but is not a directory");
+	else if (r < 0) {
+		/* Need to create it. */
+		if (mkdir(fsobj, 0700) < 0)
+			die_errno("Create $HOME/.gits/keys failed");
+	 }
+}
+
+
+static int seal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,
+	const char *sealer)
+{
+	struct child_process child;
+	char **argv;
+	char *sealer_copy;
+	int splits = 0;
+	int escape = 0;
+	int ridx, widx, tidx;
+	int cleanup = 0;
+	const char *i;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	for (i = sealer; *i; i++) {
+		if (escape)
+			escape = 0;
+		else if (*i == '\\')
+			escape = 1;
+		else if (*i == ' ')
+			splits++;
+	}
+
+	argv = xmalloc((splits + 2) * sizeof(char*));
+	argv[splits + 1] = NULL;
+
+	sealer_copy = xstrdup(sealer);
+	argv[0] = sealer_copy;
+
+	ridx = 0;
+	widx = 0;
+	tidx = 1;
+	escape = 0;
+	while (sealer_copy[ridx]) {
+		if (escape) {
+			escape = 0;
+			sealer_copy[widx++] = sealer_copy[ridx++];
+		} else if (sealer_copy[ridx] == '\\') {
+			ridx++;
+			escape = 1;
+		} else if (sealer_copy[ridx] == ' ') {
+			sealer_copy[widx++] = '\0';
+			argv[tidx++] = sealer_copy + widx;
+			ridx++;
+		} else
+			sealer_copy[widx++] = sealer_copy[ridx++];
+	}
+	sealer_copy[widx] = '\0';
+
+	memset(&child, 0, sizeof(child));
+	child.argv = (const char**)argv;
+	child.in = -1;
+	child.out = -1;
+	child.err = 0;
+	if (start_command(&child))
+		return -1;
+	cleanup = 1;
+
+	while (1) {
+		int bound;
+		fd_set rf;
+		fd_set wf;
+		int r;
+
+		FD_ZERO(&rf);
+		FD_ZERO(&wf);
+		FD_SET(child.out, &rf);
+		if (cbuffer_used(unsealed))
+			FD_SET(child.in, &wf);
+		else
+			close(child.in);
+
+		if (cbuffer_used(sealed))
+			bound = ((child.out > child.in) ? child.out :
+				child.in) + 1;
+		else
+			bound = child.out + 1;
+
+		r = select(bound, &rf, &wf, NULL, NULL);
+		if (r < 0 && r != EINTR) {
+			perror("Select");
+			goto exit_error;
+		}
+		if (r < 0) {
+			FD_ZERO(&rf);
+			FD_ZERO(&wf);
+			perror("select");
+		}
+
+		if (FD_ISSET(child.out, &rf)) {
+			r = cbuffer_read_fd(sealed, child.out);
+			if (r < 0 && errno != EINTR && errno != EAGAIN) {
+				fprintf(stderr, "Read from sealer "
+					"failed: %s", strerror(errno));
+				goto exit_error;
+			}
+			if (r < 0 && errno == EAGAIN)
+				if (!cbuffer_free(sealed)) {
+					fprintf(stderr, "Keypair too big\n");
+					goto exit_error;
+			}
+			if (r < 0)
+				perror("read");
+			if (r == 0)
+				break;
+			}
+
+			if (FD_ISSET(child.in, &wf)) {
+			r = cbuffer_write_fd(unsealed, child.in);
+			if (r < 0 && errno == EPIPE) {
+				fprintf(stderr, "Sealer exited "
+					"unexpectedly\n");
+				goto exit_error;
+			}
+			if (r < 0 && errno != EINTR && errno != EAGAIN) {
+				fprintf(stderr, "Write to sealer "
+					"failed: %s", strerror(errno));
+				goto exit_error;
+			}
+			if (r < 0)
+				perror("write");
+		}
+	}
+
+	if (finish_command(&child)) {
+		cleanup = 0;
+		goto exit_error;
+	}
+
+	close(child.in);
+	close(child.out);
+
+	return 0;
+exit_error:
+	close(child.in);
+	close(child.out);
+	return -1;
+}
+
+static void append_member(struct cbuffer *cbuf, const char *filename)
+{
+	unsigned char backing[CERT_MAX];
+	struct cbuffer *content;
+	int fd;
+	size_t size = 0;
+	unsigned char buf[2];
+
+	content = cbuffer_create(backing, CERT_MAX);
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		perror("open");
+		exit(1);
+	}
+	while (1) {
+		ssize_t r = cbuffer_read_fd(content, fd);
+		if (r < 0) {
+			if (errno == EAGAIN) {
+				if (!cbuffer_free(content)) {
+					fprintf(stderr, "Member too big.\n");
+					unlink("key.private.tmp");
+					unlink("key.public.tmp");
+					exit(1);
+				}
+			} else if (errno != EINTR) {
+				perror("read");
+				exit(1);
+			}
+		} else if (r == 0) {
+			break;
+		} else
+			size += r;
+	}
+	close(fd);
+
+	buf[0] = (unsigned char)((size >> 8) & 0xFF);
+	buf[1] = (unsigned char)((size) & 0xFF);
+	if (cbuffer_write(cbuf, buf, 2) < 0) {
+		fprintf(stderr, "Certificate too big (can't write member header).\n");
+		unlink("key.private.tmp");
+		unlink("key.public.tmp");
+		exit(1);
+	}
+	if (cbuffer_move(cbuf, content, size) < 0) {
+		fprintf(stderr, "Certificate too big (can't write member of %u "
+			"bytes).\n", size);
+		unlink("key.private.tmp");
+		unlink("key.public.tmp");
+		exit(1);
+	}
+
+	cbuffer_destroy(content);
+}
+
+
+static char *escape(char *s)
+{
+	char *ans;
+	int ridx = 0, widx = 0;
+
+	ans = xmalloc(2 * strlen(s) + 1);
+	while (s[ridx]) {
+		if (s[ridx] == '\\') {
+			ans[widx++] = '\\';
+			ans[widx++] = '\\';
+			ridx++;
+		} else if (s[ridx] == ' ') {
+			ans[widx++] = '\\';
+			ans[widx++] = ' ';
+			ridx++;
+		} else {
+			ans[widx++] = s[ridx++];
+		}
+	}
+	ans[widx] = '\0';
+	free(s);
+	return ans;
+}
+
+static int check_name(char* s)
+{
+	size_t x;
+	x = strspn(s, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+	if (!x || s[x]) {
+		return -1;
+	}
+	return 0;
+}
+
+static int check_comment(char* s)
+{
+	char *at;
+	at = strchr(s, '(');
+	if (at)
+		return -1;
+	at = strchr(s, '\\');
+	if (at)
+		return -1;
+	at = strchr(s, ')');
+	if (at)
+		return -1;
+	at = s;
+	while (*at >= 32 && *at <= 126)
+		at++;
+	if (*at)
+		return -1;
+	return 0;
+}
+
+static int check_email(char* s)
+{
+	size_t x;
+	char *at;
+	at = strchr(s, '@');
+	if (!at)
+		return -1;
+	x = strspn(s, ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+	if (s[x] != '@')
+		return -1;
+	if (!at[1])
+		return -1;
+	x = strspn(at + 1, ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmmnopqrstuvwxyz!#$%&'*+-/=?^_`{|}~");
+	if (at[x + 1])
+		return -1;
+	return 0;
+}
+
+#define BUFSIZE 8192
+
+void write_cert(int server_mode)
+{
+	unsigned char backing1[CERT_MAX];
+	unsigned char backing2[CERT_MAX];
+	unsigned char keytemp[CERT_MAX];
+	struct cbuffer *unsealed;
+	struct cbuffer *sealed;
+	unsigned char *buf = (unsigned char*)"GITSUCERT";
+	unsigned char *buf2 = (unsigned char*)"GITSSCERT\x00\x03gpg";
+	char *name;
+	char fbuffer[BUFSIZE];
+	int fd;
+	int do_seal = 0;
+	int keylen = 1024;
+	char *realname;
+	char *comment;
+	char *email;
+	FILE *script;
+
+	if (!server_mode)
+		ensure_leading_directories();
+
+	unsealed = cbuffer_create(backing1, CERT_MAX);
+	sealed = cbuffer_create(backing2, CERT_MAX);
+
+reask_length:
+	printf("1) 1024 bit key\n");
+	printf("2) 2048 bit key\n");
+	printf("3) 3072 bit key\n");
+	name = prompt_string("Pick key length", 0);
+	if (!strcmp(name, "1"))
+		keylen = 1024;
+	else if (!strcmp(name, "2"))
+		keylen = 2048;
+	else if (!strcmp(name, "3"))
+		keylen = 3072;
+	else {
+		fprintf(stderr, "Bad choice\n");
+		goto reask_length;
+	}
+
+ask_name:
+	realname = prompt_string("Enter name to put into key", 0);
+	if (check_name(realname) < 0) {
+		fprintf(stderr, "Bad name\n");
+		goto ask_name;
+	}
+ask_comment:
+	comment = prompt_string("Enter comment to put into key", 0);
+	if (check_comment(comment) < 0) {
+		fprintf(stderr, "Bad comment\n");
+		goto ask_comment;
+	}
+ask_email:
+	email = prompt_string("Enter E-mail address to put into key", 0);
+	if (check_email(email) < 0) {
+		fprintf(stderr, "Bad E-mail address\n");
+		goto ask_email;
+	}
+
+	script = fopen("key.script.tmp", "w");
+	if (!script)
+		die("Can't create key script");
+	fprintf(script, "Key-Type: DSA\n");
+	fprintf(script, "Key-Length: %i\n", keylen);
+	fprintf(script, "Name-Real: %s\n", realname);
+	if (*comment)
+		fprintf(script, "Name-Comment: %s\n", comment);
+	fprintf(script, "Name-Email: %s\n", email);
+	fprintf(script, "Expire-Date: 0\n");
+	fprintf(script, "%%pubring key.public.tmp\n");
+	fprintf(script, "%%secring key.private.tmp\n");
+	fprintf(script, "%%commit\n");
+	fprintf(script, "%%echo done\n");
+	fclose(script);
+
+	if (system("gpg --batch --gen-key key.script.tmp")) {
+		unlink("key.private.tmp");
+		unlink("key.public.tmp");
+		unlink("key.script.tmp");
+		die("Can't generate key");
+	}
+	unlink("key.script.tmp");
+
+	append_member(unsealed, "key.private.tmp");
+	unlink("key.private.tmp");
+	append_member(unsealed, "key.public.tmp");
+	unlink("key.public.tmp");
+
+reask_seal:
+	if (server_mode)
+		goto no_seal;
+	printf("1) Don't seal key\n");
+	printf("2) Seal using password (gpg)\n");
+	printf("3) Seal using keypair (gpg)\n");
+	name = prompt_string("Pick sealing method", 0);
+	if (!strcmp(name, "1"))
+		do_seal = 0;
+	else if (!strcmp(name, "2"))
+		do_seal = 1;
+	else if (!strcmp(name, "3"))
+		do_seal = 2;
+	else {
+		fprintf(stderr, "Bad choice");
+		goto reask_seal;
+	}
+no_seal:
+	keylen = cbuffer_read_max(unsealed, keytemp, CERT_MAX);
+	cbuffer_write(unsealed, keytemp, keylen);
+
+	if (do_seal == 1) {
+		cbuffer_write(sealed, (unsigned char*)buf2, 14);
+		if (seal_cert(sealed, unsealed, "gpg --symmetric "
+			"--force-mdc") < 0) {
+			cbuffer_clear(sealed);
+			cbuffer_clear(unsealed);
+			cbuffer_write(unsealed, keytemp, keylen);
+			fprintf(stderr, "Sealing failed.\n");
+			goto reask_seal;
+		}
+	} else if (do_seal == 2) {
+		char* hint;
+		cbuffer_write(sealed, (unsigned char*)buf2, 14);
+
+		hint = prompt_string("Seal using whose key", 0);
+		hint = escape(hint);
+		sprintf(fbuffer, "gpg --encrypt --recipient %s "
+			"--force-mdc", hint);
+		free(hint);
+
+		if (seal_cert(sealed, unsealed, fbuffer) < 0) {
+			cbuffer_clear(sealed);
+			cbuffer_clear(unsealed);
+			cbuffer_write(unsealed, keytemp, keylen);
+			fprintf(stderr, "Sealing failed.\n");
+			goto reask_seal;
+		}
+	} else {
+		cbuffer_write(sealed, (unsigned char*)buf, 9);
+		cbuffer_move_nolimit(sealed, unsealed);
+		if (!cbuffer_free(sealed))
+			die("Key too large");
+	}
+
+retry_name:
+	if (server_mode) {
+		name = prompt_string("Enter filename to save key as", 0);
+		strcpy(fbuffer, name);
+	} else {
+		name = prompt_string("Enter name for key", 0);
+		if (strcspn(name, "@:/[") < strlen(name)) {
+			fprintf(stderr, "Bad name\n");
+			goto retry_name;
+		}
+		sprintf(fbuffer, "%s/.gits/keys/%s", get_home(), name);
+	}
+	if (!strcmp(name, "")) {
+		fprintf(stderr, "Bad name\n");
+		goto retry_name;
+	}
+
+	fd = open(fbuffer, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	if (fd < 0) {
+		fprintf(stderr, "Can't open %s: %s\n", fbuffer,
+			strerror(errno));
+		goto retry_name;
+	}
+	while (cbuffer_used(sealed)) {
+		ssize_t r = cbuffer_write_fd(sealed, fd);
+		if (r < 0 && errno != EINTR) {
+			perror("write");
+			exit(1);
+		}
+	}
+	close(fd);
+}
diff --git a/git-over-tls/prompt.c b/git-over-tls/prompt.c
new file mode 100644
index 0000000..380156a
--- /dev/null
+++ b/git-over-tls/prompt.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "prompt.h"
+#include <termios.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static int tmpfd;
+
+static void sigint(int x);
+
+void echo_off(int fd)
+{
+	struct termios t;
+
+	if (tcgetattr(fd, &t) < 0)
+		die_errno("Can't read terminal settings");
+
+	t.c_lflag &= ~ECHO;
+
+	tmpfd = fd;
+	signal(SIGINT, sigint);
+
+	if (tcsetattr(fd, TCSANOW, &t) < 0)
+		die_errno("Can't write terminal settings");
+}
+
+void echo_on(int fd)
+{
+	struct termios t;
+
+	if (tcgetattr(fd, &t) < 0)
+		die_errno("Can't read terminal settings");
+
+	t.c_lflag |= ECHO;
+
+	if (tcsetattr(fd, TCSANOW, &t) < 0)
+		die_errno("Can't write terminal settings");
+
+	signal(SIGINT, SIG_DFL);
+}
+
+static void sigint(int x)
+{
+	echo_on(tmpfd);
+	exit(1);
+}
+
+#define PROMPTBUF 8192
+
+char *prompt_string(const char *prompt, int without_echo)
+{
+	char ansbuf[8192];
+	char *ans;
+	int fd;
+	FILE* tty;
+
+	fd = open("/dev/tty", O_RDWR);
+	if (fd < 0)
+		die_errno("Can't open /dev/tty for password prompt");
+
+	tty = xfdopen(fd, "r+");
+
+	fprintf(tty, "%s: ", prompt);
+	fflush(tty);
+	if (without_echo)
+		echo_off(fd);
+
+	if (!fgets(ansbuf, 8190, tty)) {
+		if (without_echo)
+			echo_on(fd);
+		die("Can't read answer");
+	}
+
+	if (without_echo) {
+		fprintf(tty, "\n");
+		echo_on(fd);
+	}
+
+	if (*ansbuf && ansbuf[strlen(ansbuf) - 1] == '\n')
+		ansbuf[strlen(ansbuf) - 1] = '\0';
+
+	fclose(tty);
+
+	ans = xstrdup(ansbuf);
+	return ans;
+}
diff --git a/git-over-tls/prompt.h b/git-over-tls/prompt.h
new file mode 100644
index 0000000..34fc13a
--- /dev/null
+++ b/git-over-tls/prompt.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _prompt__h__included__
+#define _prompt__h__included__
+
+/* Turn terminal echo on specified fd off. */
+void echo_off(int fd);
+/* Turn terminal echo on specified fd on. */
+void echo_on(int fd);
+/* Prompt string from user and return mallocced copy of it. */
+char *prompt_string(const char *prompt, int without_echo);
+
+#endif
diff --git a/git-over-tls/srp_askpass.c b/git-over-tls/srp_askpass.c
new file mode 100644
index 0000000..0c0da36
--- /dev/null
+++ b/git-over-tls/srp_askpass.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "srp_askpass.h"
+#include "prompt.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#define PROMPTBUF 8192
+#define CMDBUFSIZE 16384
+
+/* Use GITS_ASKPASS to ask for password. */
+static char *get_password_via_external(const char *username,
+	const char *prog)
+{
+	static char buffer[PROMPTBUF + 1];
+	static char cmdbuffer[CMDBUFSIZE + 1];
+	char *ans;
+	int escape = 0;
+	int idx;
+	int len;
+	int widx = 0;
+	FILE *out;
+
+	if (strchr(username, '\"'))
+		die("Can't prompt for usernames containing '\"'");
+
+	len = snprintf(buffer, PROMPTBUF + 1, "\"Enter SRP password for %s\"",
+		username);
+	if (len < 0 || len > PROMPTBUF)
+		die("SRP Username is insanely long");
+
+	for (idx = 0; prog[idx]; idx++) {
+		if (!escape && prog[idx] == '%')
+			escape = 1;
+		else if (escape && prog[idx] == 'p') {
+			if (widx + strlen(buffer) >= CMDBUFSIZE)
+				die("Command line too long");
+			strcpy(cmdbuffer + widx, buffer);
+			widx += strlen(buffer);
+		} else {
+			if (widx + 1 >= CMDBUFSIZE)
+				die("Command line too long");
+			cmdbuffer[widx++] = prog[idx];
+			escape = 0;
+		}
+	}
+	cmdbuffer[widx++] = '\0';
+
+	out = popen(cmdbuffer, "r");
+	if (!out)
+		die_errno("Can't invoke $GITS_ASKPASS");
+
+	if (!fgets(buffer, PROMPTBUF - 2, out)) {
+		die("Can't read password");
+	}
+
+	if (strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\n')
+		buffer[strlen(buffer) - 1] = '\0';
+
+	if (pclose(out))
+		die("Authentication canceled");
+
+	ans = xstrdup(buffer);
+	return ans;
+}
+
+char *get_srp_password(const char *username)
+{
+	static char buffer[PROMPTBUF + 1];
+
+	if (getenv("GITS_ASKPASS"))
+		return get_password_via_external(username,
+			getenv("GITS_ASKPASS"));
+
+	sprintf(buffer, "Enter SRP password for %s", username);
+	return prompt_string(buffer, 1);
+}
diff --git a/git-over-tls/hostkey.h b/git-over-tls/srp_askpass.h
similarity index 59%
copy from git-over-tls/hostkey.h
copy to git-over-tls/srp_askpass.h
index c0e7dfe..d7271fd 100644
--- a/git-over-tls/hostkey.h
+++ b/git-over-tls/srp_askpass.h
@@ -5,11 +5,10 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
-#ifndef _hostkey__h__included__
-#define _hostkey__h__included__
+#ifndef _srp_askpass__h__included__
+#define _srp_askpass__h__included__
 
-#include <gnutls/gnutls.h>
-
-void check_hostkey(gnutls_session_t session, const char *hostname);
+/* Get SRP password. Return is malloced */
+char *get_srp_password(const char *username);
 
 #endif
diff --git a/git-over-tls/user.c b/git-over-tls/user.c
new file mode 100644
index 0000000..2bb23f5
--- /dev/null
+++ b/git-over-tls/user.c
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "user.h"
+#include "cbuffer.h"
+#include "misc.h"
+#include <sys/select.h>
+#include <sys/time.h>
+#include <gnutls/gnutls.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#ifdef USE_TRAP_PAGING
+#include <sys/mman.h>
+#endif
+
+/*
+ * NOTE: This code may not crash, call exit or anything similar unless
+ * program state is corrupt or call parameters are completely invalid.
+ * It also may not call Git APIs.
+ */
+
+/* Main buffer size. */
+#define BUFFERSIZE 65536
+/*
+ * Error buffer can be maximum of 65535-8 bytes and must be smaller or
+ * equal in size to main buffers.
+ */
+#define ERR_BUFFERSIZE (65535-8)
+
+struct user
+{
+	/* If 1, EOF received from black in at transport level. */
+	unsigned u_black_in_eof : 1,
+	/* If 1, EOF sent to black out at transport level. */
+		u_black_out_eof : 1,
+	/* If 1, EOF received from black in at TLS level. */
+		u_black_in_d_eof : 1,
+	/* If 1, EOF sent to black out at TLS level. */
+		u_black_out_d_eof : 1,
+	/* If 1, Assume that there is more input to come from red in. */
+		u_red_assume_more : 1,
+	/* If 1, TLS is active and ready to transfer data. */
+		u_tls_active : 1,
+	/* If 1, there has been data received from red in. */
+		u_red_in_have_data : 1,
+	/*
+	 * Result of last read of decrypted data.
+	 * 0 => Read was successful or not attempted yet.
+	 * 1 => Read was blocked by insufficient input data.
+	 * 2 => Read was blocked by insufficient output space.
+	 * 3 => (Reserved)
+	 */
+		u_want_read : 2,
+	/*
+	 * Result of last write of decrypted data.
+	 * 0 => Write was successful or not attempted yet.
+	 * 1 => Write was blocked by insufficient input data.
+	 * 2 => Write was blocked by insufficient output space.
+	 * 3 => (Reserved)
+	 */
+		u_want_write : 2,
+	/*
+	 * Result of last handshake attempt.
+	 * 0 => Handshake was successful or not attempted yet.
+	 * 1 => Handshake was blocked by insufficient input data.
+	 * 2 => Handshake was blocked by insufficient output space.
+	 * 3 => (Reserved)
+	 */
+		u_want_hand : 2,
+	/* Number of bytes of error header sent (0-8). */
+		u_red_err_hdr_sent : 4,
+	/* Delay TLS failure present. 1 for handshake, 2 otherwise */
+		u_delay_tls_failure : 2,
+	/* Has seen any data received. */
+		u_seen_input_data : 1;
+
+	/* File descriptors. -1 if none. */
+	int u_black_fd;		/* Input/Output */
+	int u_red_in_fd;	/* Input */
+	int u_red_out_fd;	/* Output */
+	int u_red_err_fd;	/* Input */
+	/* The backing buffer for all transfer buffers and its size. */
+	unsigned char *u_buf_backing;
+	size_t u_buf_backing_size;
+	/* The actual transfer buffers. */
+	struct cbuffer *u_black_in_buf;
+	struct cbuffer *u_black_out_buf;
+	struct cbuffer *u_red_in_buf;
+	struct cbuffer *u_red_out_buf;
+	struct cbuffer *u_red_err_buf;
+	/* Deadline. Tv_sec is -1 if there is no deadline. */
+	struct timeval u_deadline;
+	/*
+	 * Why the connection was torn down. Delay_failure is delayed
+	 * failure that becomes real after output buffer is flushed
+	 * so that TLS alerts are sent. Also stored is delayed TLS alert
+	 * that couldn't be sent yet.
+	 */
+	int u_failure;
+	char *u_errmsg;
+	int u_delay_failure;
+	gnutls_alert_description_t u_delay_alert;
+	/* Active TLS session. NULL if no TLS. */
+	gnutls_session_t u_tls_session;
+};
+
+/* Initiate delayed failure on user. */
+static void delay_cleanup_user(struct user *user, int failure,
+	const char *error)
+{
+	/* Store the cause of termination. */
+	if (!user->u_errmsg) {
+		if (error)
+			user->u_errmsg = strdup(error);
+		else
+			user->u_errmsg = NULL;
+	}
+	user->u_delay_failure = failure;
+}
+
+/* Clean up user connection immediately. */
+static void cleanup_user(struct user *user, int failure, const char *error)
+{
+	/* If there is TLS session, deallocate its resources. */
+	if (user->u_tls_session) {
+		gnutls_deinit(user->u_tls_session);
+		user->u_tls_session = NULL;
+	}
+	/* Shutdown and close black input/output. */
+	if (user->u_black_fd >= 0) {
+		shutdown(user->u_black_fd, SHUT_WR);
+		force_close(user->u_black_fd);
+	}
+	user->u_black_fd = -1;
+
+	/* Close red inputs/outputs. */
+	if (user->u_red_in_fd >= 0)
+		force_close(user->u_red_in_fd);
+	user->u_red_in_fd = -1;
+	if (user->u_red_out_fd >= 0)
+		force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+	if (user->u_red_err_fd >= 0)
+		force_close(user->u_red_err_fd);
+	user->u_red_err_fd = -1;
+
+	/* Store the cause of termination. */
+	if (!user->u_errmsg) {
+		if (error)
+			user->u_errmsg = strdup(error);
+		else
+			user->u_errmsg = NULL;
+	}
+	user->u_failure = failure;
+}
+
+/*
+ * Process delay failure. If there is delayed failure and output buffer is
+ * empty, make it real failure and disconnect user immediately.
+ */
+static int process_delay_failure(struct user *user)
+{
+	if (user->u_failure)
+		return 0;
+	if (!cbuffer_used(user->u_black_out_buf) && user->u_delay_failure) {
+		cleanup_user(user, user->u_delay_failure, NULL);
+		return 0;
+	}
+	return 0;
+}
+
+/* Is this error from I/O syscall fatal? */
+static int is_fatal_error(int error)
+{
+	return (error != EINTR && error != EAGAIN && error != EWOULDBLOCK);
+}
+
+/* Is this error from GnuTLS I/O operation fatal? */
+static int is_fatal_tls_error(int error)
+{
+	/*
+	 * GNUTLS_E_INTERRUPTED should never be seen. since custom push and
+	 * pull functions can't return EINTR, only EAGAIN.
+	 */
+	return (error < 0 && error != GNUTLS_E_AGAIN);
+}
+
+/* Handle fatal error received from GnuTLS functions. */
+static int handle_tls_failure_code(struct user *user, int gnutls_code,
+	int handshaking)
+{
+	int x;
+	char error_buffer[8192];
+	if (gnutls_code == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+		/* Fatal alert. Get the description and format message. */
+		gnutls_alert_description_t alert;
+		alert = gnutls_alert_get(user->u_tls_session);
+		sprintf(error_buffer, "TLS alert received: %s",
+			gnutls_alert_get_name(alert));
+
+		if (alert == GNUTLS_A_BAD_RECORD_MAC && handshaking)
+			sprintf(error_buffer, "TLS alert received: %s "
+				"(incorrect password?)",
+				gnutls_alert_get_name(alert));
+
+		/* Terminate the connection. */
+		if (handshaking)
+			cleanup_user(user, USER_TLS_HAND_ERROR,
+				error_buffer);
+		else
+			cleanup_user(user, USER_TLS_ERROR,
+				error_buffer);
+		return 0;
+	}
+	/*
+	 * Alerts use handshake readyness indicator, so try to set it
+	 * to "perform I/O immediately".
+	 */
+	user->u_want_hand = 0;
+	/*
+	 * Mark that delay TLS failure is present and get the alert
+	 * that should be sent. Also set error message.
+	 */
+	user->u_delay_tls_failure = 1;
+#ifndef DISABLE_SRP
+	/* GnuTLS doesn't seem to have proper code for this. */
+	if (gnutls_code == GNUTLS_E_SRP_PWD_ERROR)
+		user->u_delay_alert = GNUTLS_A_UNKNOWN_PSK_IDENTITY;
+	else
+#endif
+		user->u_delay_alert = (gnutls_alert_description_t)
+			gnutls_error_to_alert((int)gnutls_code, &x);
+	user->u_errmsg = strdup(gnutls_strerror_name((int)gnutls_code));
+	return 1;
+}
+
+/* Handle black input activity */
+static int black_in_handler(struct user *user, fd_set *rfds)
+{
+	/* Don't attempt to read if connection has failed. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* The file descrptor must be marked readable. */
+	if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, rfds))
+		return 0;
+	/* Don't attempt to read already EOF'd file descriptor. */
+	if (user->u_black_in_eof)
+		return 0;
+
+	ssize_t r = cbuffer_read_fd(user->u_black_in_buf, user->u_black_fd);
+	FD_CLR(user->u_black_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound connection faulted! */
+		cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+	} else if (r == 0) {
+		/* Received EOF. */
+		user->u_black_in_eof = 1;
+	} else if (r > 0) {
+		/* Received some data. */
+		user->u_seen_input_data = 1;
+	}
+	return 1;
+}
+
+/* Handle black output activity. */
+static int black_out_handler(struct user *user, fd_set *wfds)
+{
+	/*
+	 * Don't attempt to write if connection has failed.  This is one of
+	 * the very few handlers still to run in delayed failure.
+	 */
+	if (user->u_failure)
+		return 0;
+	/* The file descrptor must be marked as writable. */
+	if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, wfds))
+		return 0;
+	/* Don't attempt to write if EOF has already been sent. */
+	if (user->u_black_out_eof)
+		return 0;
+
+	ssize_t r = cbuffer_write_fd(user->u_black_out_buf, user->u_black_fd);
+	FD_CLR(user->u_black_fd, wfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Outbound connection faulted! */
+		cleanup_user(user, USER_LAYER4_ERROR, strerror(errno));
+	} else if (r > 0) {
+		/* Sent some data. */
+	}
+	return 1;
+}
+
+/* Send black out EOF if needed (using TLS if it is active) */
+static int black_out_eof_handler(struct user *user)
+{
+	/* Don't operate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate anymore if EOF has been sent outbound. */
+	if (user->u_black_out_eof || user->u_black_out_d_eof)
+		return 0;
+	/* Don't activate if there can be more data from red in. */
+	if (user->u_red_assume_more || user->u_red_in_fd >= 0)
+		return 0;
+	/* Don't activate if there can be more data from red error. */
+	if (user->u_red_err_fd >= 0)
+		return 0;
+	/* Don't activate if there is more data in red input. */
+	if (cbuffer_used(user->u_red_in_buf))
+		return 0;
+	/* Don't activate if there is more data in red error. */
+	if (cbuffer_used(user->u_red_err_buf))
+		return 0;
+	/*
+	 * If not in TLS mode, don't activate if there is data in send
+	 * buffer.
+	 */
+	if (!user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+		return 0;
+
+	if (user->u_tls_session) {
+		int r;
+		/* Try to send the EOF at TLS level. */
+		r = gnutls_bye(user->u_tls_session, GNUTLS_SHUT_WR);
+		if (r == 0) {
+			/* Suceeded. Mark the connection as EOF'd. */
+			user->u_black_out_d_eof = 1;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal stream error! */
+			return handle_tls_failure_code(user, (int)r, 0);
+		}
+		/* Otherwise we failed due to non-fatal TLS error. We'll try
+		   again soon. */
+		return 0;
+	} else {
+		/* Try to send normal transport EOF. */
+		if (shutdown(user->u_black_fd, SHUT_WR) < 0) {
+			/* Failed, declare as stream error. */
+			cleanup_user(user, USER_LAYER4_ERROR,
+				strerror(errno));
+			return 0;
+		}
+		user->u_black_out_eof = 1;
+		return 1;
+	}
+	return 0; /* Should not be here. */
+}
+
+/* Send EOF to red output if needed. */
+static int red_out_eof_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate unless input EOF has been received. */
+	if (!user->u_black_in_eof && !user->u_black_in_d_eof)
+		return 0;
+	/* Don't activate anymore if red out has been closed. */
+	if (user->u_red_out_fd < 0)
+		return 0;
+	/* If not in TLS mode, don't activate if there is data in receive
+	   buffer. */
+	if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+		return 0;
+	/* Don't activate if there is more data in red output. */
+	if (cbuffer_used(user->u_red_out_buf))
+		return 0;
+
+	force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+	return 1;
+}
+
+/* Send data to file descriptor connected to red output. */
+static int red_out_handler(struct user *user, fd_set *wfds)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red out must be writable fd. */
+	if (user->u_red_out_fd < 0 || !FD_ISSET(user->u_red_out_fd, wfds))
+		return 0;
+
+	ssize_t r = cbuffer_write_fd(user->u_red_out_buf, user->u_red_out_fd);
+	FD_CLR(user->u_red_out_fd, wfds);
+	if (r < 0 && errno == EPIPE) {
+		/* EPIPE is treated as special type of EOF. We need to read
+		   EOFs from process. */
+		force_close(user->u_red_out_fd);
+		user->u_red_out_fd = -1;
+		return 1;
+	} else if (r < 0 && is_fatal_error(errno)) {
+		/* Red out connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+		return 0;
+	} else if (r > 0) {
+		/* Sent some data to red output. */
+		return 1;
+	}
+	/* Can't come here. */
+	return 0;
+}
+
+/* Recieve data from file descriptor connected to red input. */
+static int red_in_handler(struct user *user, fd_set *rfds)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red in must be readable fd. */
+	if (user->u_red_in_fd < 0 || !FD_ISSET(user->u_red_in_fd, rfds))
+		return 0;
+
+	ssize_t r = cbuffer_read_fd(user->u_red_in_buf, user->u_red_in_fd);
+	FD_CLR(user->u_red_in_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound red connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+		return 0;
+	} else if (r == 0) {
+		/* Close it so we eventually send EOF. */
+		user->u_red_assume_more = 0;
+		force_close(user->u_red_in_fd);
+		user->u_red_in_fd = -1;
+		return 1;
+	} else if (r > 0) {
+		/* Received some data. */
+		user->u_red_in_have_data = 1;
+
+		/* Clear the error buffer. */
+		cbuffer_clear(user->u_red_err_buf);
+	}
+	return 1;
+}
+
+#define DUMMYBUFSIZE 256
+
+/* Receive data from file descriptor connected to red error. */
+static int red_err_handler(struct user *user, fd_set *rfds)
+{
+	ssize_t r;
+	char buf[DUMMYBUFSIZE];
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Red err must be readable fd. */
+	if (user->u_red_err_fd < 0 || !FD_ISSET(user->u_red_err_fd, rfds))
+		return 0;
+
+	if (!user->u_red_in_have_data) {
+		/* Read the red error for real. */
+		r = cbuffer_read_fd(user->u_red_err_buf, user->u_red_err_fd);
+	} else {
+		/* Just do dummy read and discard the reuslts. */
+		r = read(user->u_red_err_fd, buf, DUMMYBUFSIZE);
+	}
+	FD_CLR(user->u_red_err_fd, rfds);
+	if (r < 0 && is_fatal_error(errno)) {
+		/* Inbound red connection faulted! */
+		cleanup_user(user, USER_RED_FAILURE, strerror(errno));
+	} else if (r == 0) {
+		/* Close it so we eventually send EOF. */
+		force_close(user->u_red_err_fd);
+		user->u_red_err_fd = -1;
+	} else if (r > 0) {
+		/*
+		 * Received some data.
+		 *
+		 * HACK ALERT: Some systems seem to like to send these
+		 * non-errors on error channel. Ignore those and treat
+		 * them like data from stdout. (35 is length of that
+		 * string).
+		 */
+		const char* tmsg = "Initialized empty Git repository in";
+		unsigned char tmp[35];
+		if (cbuffer_peek(user->u_red_err_buf, tmp, 35) >= 0 &&
+			!strncmp(tmsg, (char*)tmp, 35)) {
+			user->u_red_in_have_data = 1;
+			cbuffer_clear(user->u_red_err_buf);
+		}
+	}
+	return 1;
+}
+
+/* Terminate the whole connection if needed EOFs have been seen. */
+static int connection_eof_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if red out is still open. */
+	if (user->u_red_out_fd >= 0)
+		return 0;
+	/* Don't activate unless output EOF has been asserted. */
+	if (!user->u_black_out_eof && !user->u_black_out_d_eof)
+		return 0;
+	/* Don't assert in TLS mode unless output buffer is empty. */
+	if (user->u_tls_session && cbuffer_used(user->u_black_out_buf))
+		return 0;
+
+	/* Both half-connections have ended. End the entiere connection. */
+	cleanup_user(user, USER_CONNECTION_END, NULL);
+	return 1;
+}
+
+/*
+ * Should operation take place given last operation failed practicular way?
+ * Direction2 is 0 if last operation didn't fail, 1 if it failed due to
+ * insufficient data to read, 2 if it failed due to insufficient space to
+ * write.
+ */
+static int gnutls_blacks_activate2(struct user *user, int direction2)
+{
+	/*
+	 * If not attempted yet, or last time it was successful, attempt
+	 * immediately again.
+	 */
+	if (direction2 == 0)
+		return 1;
+	/*
+	 * If last time it failed due to insufficient read space, try
+	 * again only if there is some data in read buffers now.
+	 */
+	if (direction2 == 1) {
+		if (cbuffer_used(user->u_black_in_buf) != 0)
+			return 1;
+		else if (!user->u_black_in_eof)
+			return 0;
+		else if (user->u_tls_session && user->u_seen_input_data)
+			cleanup_user(user, USER_TLS_ERROR, "Connection "
+				"closed unexpectedly");
+		else if (user->u_tls_session)
+			cleanup_user(user, USER_LAYER4_ERROR, "Connection "
+				"broke before any data was received");
+		else
+			return 0;
+	}
+	/*
+	 * If last time it failed due to insufficient write space, try
+	 * again only if there is some space in write buffers now.
+	 */
+	if (direction2 == 2)
+		return (cbuffer_free(user->u_black_out_buf) != 0);
+	return 0;
+}
+
+/* Copy or decrypt data from black input to red output. */
+static int black_to_red_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if there's TLS but it isn't ready yet. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Don't activate if there's no space in red out buffer. */
+	if (!cbuffer_free(user->u_red_out_buf))
+		return 0;
+	/* Don't activate if TLS-level EOF has been received. */
+	if (user->u_black_in_d_eof)
+		return 0;
+	/* Check that we don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_read))
+		return 0;
+
+	if (!user->u_tls_session) {
+		/*
+		 * no-TLS case. Just compute how much data we can move and
+		 * then just move it from black in buffer to red out buffer.
+		 */
+		size_t amount;
+		amount = cbuffer_move_nolimit(user->u_red_out_buf,
+			user->u_black_in_buf);
+		if (amount > 0) {
+			/* Ok, some data to move. Just move it. */
+			user->u_want_read = 0;
+			return 1;
+		} else {
+			/*
+			 * No data to transfer. Mark operation failed due to
+			 * insufficient data to read.
+			 */
+			user->u_want_read = 1;
+			return 0;
+		}
+	} else {
+		unsigned char *ptr;
+		size_t size;
+		ssize_t r;
+
+		/*
+		 * Compute the segment for writing data to. And if we do get
+		 * such segment, receive data to it. The size == 0 case should
+		 * not happen because we checked that there's space in red
+		 * out buffer above.
+		 */
+		cbuffer_fill_w_segment(user->u_red_out_buf, &ptr, &size);
+		r = gnutls_record_recv(user->u_tls_session, ptr, size);
+		if (r > 0) {
+			/* Received TLS data. */
+			cbuffer_commit_w_segment(user->u_red_out_buf, r);
+			user->u_want_read = 0;
+			return 1;
+		} else if (r == 0) {
+			/* Received TLS EOF. */
+			user->u_black_in_d_eof = 1;
+			user->u_want_read = 0;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary read failure. */
+			user->u_want_read = 1 + gnutls_record_get_direction(
+				user->u_tls_session);
+			return 0;
+		}
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/* Copy or encrypt data from red input to black output. */
+static int red_to_black_handler(struct user *user)
+{
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is present but not active. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Require data in red in buffer. */
+	if (!cbuffer_used(user->u_red_in_buf))
+		return 0;
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_write))
+		return 0;
+
+	if (!user->u_tls_session) {
+		/*
+		 * No-TLS case. Just find maximum amount of data to move and
+		 * then move the data.
+		 */
+		size_t amount;
+		amount = cbuffer_move_nolimit(user->u_black_out_buf,
+			user->u_red_in_buf);
+		if (amount > 0) {
+			/* Ok, some data to move. Just move it. */
+			user->u_want_write = 0;
+			return 1;
+		} else {
+			/*
+			 * No data to transfer. Mark operation failed due to
+			 * insufficient space to write.
+			 */
+			user->u_want_write = 2;
+			return 0;
+		}
+	} else {
+		unsigned char *ptr;
+		size_t size;
+		ssize_t r = 0;
+
+		/*
+		 * Compute the segment for reading data to. And if we do get
+		 * such segment, send data from it. The size == 0 case should
+		 * not happen because we checked that there's data in red
+		 * in buffer above.
+		 */
+		cbuffer_fill_r_segment(user->u_red_in_buf, &ptr, &size);
+		if (!user->u_want_write)
+			/* Last was success, just send new record. */
+			r = gnutls_record_send(user->u_tls_session, ptr,
+				size);
+		else
+			/* Last time it failed, try to resend the record. */
+			r = gnutls_record_send(user->u_tls_session, NULL, 0);
+		if (r > 0) {
+			/* Sent some TLS data. */
+			cbuffer_commit_r_segment(user->u_red_in_buf, r);
+			user->u_want_write = 0;
+			return 1;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary send failure. */
+			user->u_want_write = 1 + gnutls_record_get_direction(user->u_tls_session);
+			return 0;
+		}
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+static const char hexes[] = "0123456789abcdef";
+
+#define TMPBUFSIZE 256
+
+/* Copy or encrypt data from red error to black out. */
+static int rederr_to_black_handler(struct user *user)
+{
+	unsigned char hdrbuf[8];
+	unsigned char buffer[TMPBUFSIZE];
+	size_t bufusage = 0;
+	size_t pcktsize;
+	unsigned char *segstart;
+	size_t seglen;
+	size_t maxread;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is not yet ready. */
+	if (user->u_tls_session && !user->u_tls_active)
+		return 0;
+	/* Don't activate if there's no data in red error. */
+	if (!cbuffer_used(user->u_red_err_buf))
+		return 0;
+	/* Don't activate if red in hasn't been closed. */
+	if (user->u_red_in_fd >= 0)
+		return 0;
+	/* Don't activate if red error can have more data. */
+	if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf))
+		return 0;
+	/* Don't fail like last write. */
+	if (!gnutls_blacks_activate2(user, user->u_want_write))
+		return 0;
+
+	/* Safety hatch. Truncate error if too long. */
+	if (!cbuffer_free(user->u_red_err_buf) && user->u_red_err_fd >= 0) {
+		force_close(user->u_red_err_fd);
+		user->u_red_err_fd = -1;
+	}
+
+	/* Fill the header. */
+	pcktsize = 8 + cbuffer_used(user->u_red_err_buf);
+	hdrbuf[0] = hexes[pcktsize / 4096 % 16];
+	hdrbuf[1] = hexes[pcktsize / 256 % 16];
+	hdrbuf[2] = hexes[pcktsize / 16 % 16];
+	hdrbuf[3] = hexes[pcktsize % 16];
+	hdrbuf[4] = 'E';
+	hdrbuf[5] = 'R';
+	hdrbuf[6] = 'R';
+	hdrbuf[7] = ' ';
+	/*
+	 * If header hasn't been sent yet, copy the remainder of header to
+	 * transfer buffer.
+	 */
+	if (user->u_red_err_hdr_sent < 8) {
+		memcpy(buffer, hdrbuf + user->u_red_err_hdr_sent,
+			8 - user->u_red_err_hdr_sent);
+		bufusage = (8 - user->u_red_err_hdr_sent);
+	}
+	maxread = TMPBUFSIZE - bufusage;
+
+	/*
+	 * Request read segment out of red error. There's always data in
+	 * red error buffer by checks above. Then copy as much data from
+	 * it as possible.
+	 */
+	cbuffer_fill_r_segment(user->u_red_err_buf, &segstart, &seglen);
+	if (maxread >= seglen) {
+		memcpy(buffer + bufusage, segstart, seglen);
+		bufusage += seglen;
+	} else {
+		memcpy(buffer + bufusage, segstart, maxread);
+		bufusage += maxread;
+	}
+
+	/*
+	 * Bufusage is always positive, since its either equal to seglen
+	 * or maxread, and neither can be zero.
+	 */
+	if (!user->u_tls_session && !user->u_red_in_have_data) {
+		/*
+		 * Compute maximum amount of data that can be copied to
+		 * send buffer (no TLS case).
+		 */
+		bufusage = cbuffer_write_max(user->u_black_out_buf, buffer,
+			bufusage);
+		if (bufusage > 0)
+			user->u_want_write = 0;
+		else {
+			/* No space, mark it failed due to write. */
+			user->u_want_write = 2;
+			return 0;
+		}
+	} else if (!user->u_red_in_have_data) {
+		ssize_t r;
+
+		if (!user->u_want_write)
+			/* Last was success, just send new record. */
+			r = gnutls_record_send(user->u_tls_session, buffer,
+				bufusage);
+		else
+			/* Last time it failed, try to resend the record. */
+			r = gnutls_record_send(user->u_tls_session, NULL, 0);
+		if (r > 0) {
+			/* Successfully sent data. Adjust bufusage. */
+			bufusage = r;
+			user->u_want_write = 0;
+		} else if (is_fatal_tls_error((int)r)) {
+			/* Fatal TLS error. */
+			return handle_tls_failure_code(user, (int)r, 0);
+		} else if (r < 0) {
+			/* Temporary failure. Mark the failure. */
+			user->u_want_write = 1 + gnutls_record_get_direction(
+				user->u_tls_session);
+			bufusage = 0;
+			return 0;
+		}
+	}
+
+	/* Now bufusage is set to amount of bytes sent. ACK the data sent. */
+	if ((size_t)user->u_red_err_hdr_sent + bufusage < 8) {
+		/* Partially sent header. ACK the header sent. */
+		user->u_red_err_hdr_sent += bufusage;
+		bufusage = 0;
+		return 1;
+	} else {
+		/*
+		 * Completely sent the header. ACK rest of it and substract
+		 * it from actual data sent.
+		 */
+		bufusage -= (8 - user->u_red_err_hdr_sent);
+		user->u_red_err_hdr_sent = 8;
+	}
+	/* ACK the actual error data sent. */
+	if (bufusage > 0)
+		cbuffer_commit_r_segment(user->u_red_err_buf, bufusage);
+
+	return 1;
+}
+
+/* Try to send delayed alert. */
+static int tls_alert_handler(struct user *user)
+{
+	int r;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't do this without delayed tls failure. */
+	if (!user->u_delay_tls_failure)
+		return 0;
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_hand))
+		return 0;
+
+	r = gnutls_alert_send(user->u_tls_session, GNUTLS_AL_FATAL,
+		user->u_delay_alert);
+	if (r == 0) {
+		/* The user->u_delay_tls_failure may be 2 too. */
+		if (user->u_delay_tls_failure == 1)
+			delay_cleanup_user(user, USER_TLS_HAND_ERROR,
+				"Fatal alert sent");
+		else
+			delay_cleanup_user(user, USER_TLS_ERROR,
+				"Fatal alert sent");
+		return 0;
+	} else if (is_fatal_tls_error(r)) {
+		const char *err = gnutls_strerror_name((int)r);
+		delay_cleanup_user(user, USER_TLS_ERROR, err);
+		return 1;
+	} else if (r < 0) {
+		/* Still needs more attempts to send. */
+		user->u_want_hand = 1 + gnutls_record_get_direction(
+			user->u_tls_session);
+		return 0;
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/* Try to handshake TLS connection. */
+static int handshake_handler(struct user *user)
+{
+	int r;
+
+	/* Don't activate on already failed connections. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Don't activate if TLS is ready or no TLS. */
+	if (user->u_tls_active || !user->u_tls_session)
+		return 0;
+	/* Don't handshake anoymore with delayed TLS failure. */
+	if (user->u_delay_tls_failure)
+		return 0;
+	/* Don't fail like last time. */
+	if (!gnutls_blacks_activate2(user, user->u_want_hand))
+		return 0;
+
+	r = gnutls_handshake(user->u_tls_session);
+	if (r == 0) {
+		/* Handshake completed, TLS ready. */
+		user->u_want_hand = 0;
+		user->u_tls_active = 1;
+		return 1;
+	} else if (is_fatal_tls_error(r)) {
+		/* Fatal TLS error. */
+		return handle_tls_failure_code(user, (int)r, 1);
+	} else if (r < 0) {
+		/* Still needs more handshaking. */
+		user->u_want_hand = 1 + gnutls_record_get_direction(
+			user->u_tls_session);
+		return 0;
+	}
+	/* Can't really come here. */
+	return 0;
+}
+
+/*
+ * Compare two timevals. Returns -1 if first is first, 0 if same, 1 otherwise
+ */
+static int tv_compare(const struct timeval *tv1, const struct timeval *tv2)
+{
+	if (tv1->tv_sec == -1)
+		return (tv2->tv_sec == -1) ? 0 : 1;
+	if (tv2->tv_sec == -1)
+		return -1;
+	if (tv1->tv_sec > tv2->tv_sec)
+		return 1;
+	if (tv1->tv_sec < tv2->tv_sec)
+		return -1;
+	if (tv1->tv_usec > tv2->tv_usec)
+		return 1;
+	if (tv1->tv_usec < tv2->tv_usec)
+		return -1;
+	return 0;
+}
+
+/* Handle timeouts on connection. */
+static int timeout_handler(struct user *user)
+{
+	struct timeval t;
+
+	gettimeofday(&t, NULL);
+
+	/* Don't activate on already failed connection. */
+	if (user->u_failure || user->u_delay_failure)
+		return 0;
+	/* Is it time to activate? */
+	if (tv_compare(&user->u_deadline, &t) >= 0)
+		return 0;
+
+	cleanup_user(user, USER_TIMEOUT, "Request timeout");
+	return 0;
+}
+
+/* GnuTLS push function. */
+static ssize_t gnutls_push_data(gnutls_transport_ptr_t ptr, const void *data,
+	size_t size)
+{
+	struct user *user = (struct user*)ptr;
+
+	/* Compute maximum transfer. */
+	if (size > cbuffer_free(user->u_black_out_buf))
+		size = cbuffer_free(user->u_black_out_buf);
+
+	/* If no data can be read, send EAGAIN. */
+	if (size == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	cbuffer_write(user->u_black_out_buf, (unsigned char*)data, size);
+	return size;
+}
+
+/* GnuTLS pull function. */
+static ssize_t gnutls_pull_data(gnutls_transport_ptr_t ptr, void *data,
+	size_t size)
+{
+	struct user *user = (struct user*)ptr;
+
+	/* Compute maximum transfer. */
+	if (size > cbuffer_used(user->u_black_in_buf))
+		size = cbuffer_used(user->u_black_in_buf);
+
+	/* If no data can be written, send EAGAIN. */
+	if (size == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	cbuffer_read(user->u_black_in_buf, (unsigned char*)data, size);
+	return size;
+
+}
+
+void user_configure_tls(struct user *user, gnutls_session_t session)
+{
+	user->u_tls_session = session;
+	user->u_want_hand = 0;
+	user->u_tls_active = 0;
+	/* Configure the TLS session transport level. */
+	gnutls_transport_set_ptr(session, user);
+	gnutls_transport_set_push_function(session, gnutls_push_data);
+	gnutls_transport_set_pull_function(session, gnutls_pull_data);
+	gnutls_transport_set_lowat(session, 0);
+	/*
+	 * For security reasons, clear the red output as that can no
+	 * longer be trusted.
+	 */
+	cbuffer_clear(user->u_red_out_buf);
+}
+
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+	fd_set *wfds, struct timeval *deadline)
+{
+	/*
+	 * Execute all service handlers in case they alter what
+	 * files should be waited on.
+	 */
+	user_service_nofd(user);
+
+	/* Adjust deadline. */
+	if (tv_compare(deadline, &user->u_deadline) > 0)
+		*deadline = user->u_deadline;
+
+	if (user->u_red_out_fd >= 0 && cbuffer_used(user->u_red_out_buf)) {
+		/* Red out is writable. */
+		FD_SET(user->u_red_out_fd, wfds);
+		if (*bound <= user->u_red_out_fd)
+			*bound = user->u_red_out_fd + 1;
+	}
+	if (user->u_black_fd >= 0 && !user->u_black_in_eof &&
+		/* Black in is readable. */
+		cbuffer_free(user->u_black_in_buf)) {
+		FD_SET(user->u_black_fd, rfds);
+		if (*bound <= user->u_black_fd)
+			*bound = user->u_black_fd + 1;
+	}
+	/* Intentionally bias red to black towards writing to black. */
+	if (user->u_black_fd >= 0 && !user->u_black_out_eof &&
+		/* Black out is writable. */
+		cbuffer_used(user->u_black_out_buf)) {
+		FD_SET(user->u_black_fd, wfds);
+		if (*bound <= user->u_black_fd)
+			*bound = user->u_black_fd + 1;
+	} else if (user->u_red_in_fd >= 0 &&
+		cbuffer_free(user->u_red_in_buf)) {
+		/* Red in is readable. */
+		FD_SET(user->u_red_in_fd, rfds);
+		if (*bound <= user->u_red_in_fd)
+			*bound = user->u_red_in_fd + 1;
+	}
+	if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) {
+		/* Red err is readable. */
+		FD_SET(user->u_red_err_fd, rfds);
+		if (*bound <= user->u_red_err_fd)
+			*bound = user->u_red_err_fd + 1;
+	}
+}
+
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds)
+{
+	int newr = 1;
+
+	/* Do this until no service handler makes any progress. */
+	while (newr) {
+		newr = 0;
+		newr = newr | black_in_handler(user, rfds);
+		newr = newr | black_out_handler(user, wfds);
+		newr = newr | red_in_handler(user, rfds);
+		newr = newr | red_out_handler(user, wfds);
+		newr = newr | red_err_handler(user, rfds);
+		newr = newr | black_out_eof_handler(user);
+		newr = newr | red_out_eof_handler(user);
+		newr = newr | connection_eof_handler(user);
+		newr = newr | black_to_red_handler(user);
+		newr = newr | red_to_black_handler(user);
+		newr = newr | rederr_to_black_handler(user);
+		newr = newr | timeout_handler(user);
+		newr = newr | handshake_handler(user);
+		newr = newr | tls_alert_handler(user);
+		newr = newr | process_delay_failure(user);
+	}
+}
+
+void user_service_nofd(struct user *user)
+{
+	fd_set rfds;
+	fd_set wfds;
+
+	/* Clear the fd sets, we don't do I/O here. */
+	FD_ZERO(&rfds);
+	FD_ZERO(&wfds);
+	user_service(user, &rfds, &wfds);
+}
+
+gnutls_session_t user_get_tls(struct user *user)
+{
+	if (user->u_tls_session && user->u_tls_active)
+		return user->u_tls_session;
+	else
+		return NULL;
+}
+
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err)
+{
+	user->u_red_in_fd = red_in;
+	user->u_red_out_fd = red_out;
+	user->u_red_err_fd = red_err;
+	if (red_in >= 0)
+		fcntl(red_in, F_SETFL, fcntl(red_in, F_GETFL) | O_NONBLOCK);
+	if (red_out >= 0)
+		fcntl(red_out, F_SETFL, fcntl(red_out, F_GETFL) | O_NONBLOCK);
+	if (red_err >= 0)
+		fcntl(red_err, F_SETFL, fcntl(red_err, F_GETFL) | O_NONBLOCK);
+}
+
+void user_clear_red_io(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		force_close(user->u_red_out_fd);
+	user->u_red_out_fd = -1;
+}
+
+struct cbuffer *user_get_red_in(struct user *user)
+{
+	if (user->u_red_in_fd >= 0)
+		return NULL;
+	else
+		return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		return NULL;
+	else
+		return user->u_red_out_buf;
+}
+
+int user_get_failure(struct user *user)
+{
+	return user->u_failure;
+}
+
+const char *user_get_error(struct user *user)
+{
+	return user->u_errmsg;
+}
+
+void user_send_red_in_eof(struct user *user)
+{
+	user->u_red_assume_more = 0;
+}
+
+struct cbuffer *user_get_red_err(struct user *user)
+{
+	if (user->u_red_err_fd >= 0 || user->u_red_in_have_data)
+		return NULL;
+	else
+		return user->u_red_err_buf;
+}
+
+void user_clear_deadline(struct user *user)
+{
+	user->u_deadline.tv_sec = -1;
+}
+
+size_t round_up(size_t base, size_t divide)
+{
+	return base + (divide - base % divide) % divide;
+}
+
+struct user *user_create(int black_fd, unsigned deadline_secs)
+{
+	struct timeval t;
+	size_t offset[5];
+#ifdef USE_TRAP_PAGING
+	size_t trap[6];
+#endif
+	int i;
+
+	for (i = 0; i < 5; i++)
+		offset[i] = i * BUFFERSIZE;
+
+	fcntl(black_fd, F_SETFL, fcntl(black_fd, F_GETFL) | O_NONBLOCK);
+
+	/* Compute the deadline field value. */
+	gettimeofday(&t, NULL);
+	t.tv_sec += deadline_secs;
+
+	/* How much to allocate for buffers? */
+	int r = 0;
+	size_t allocsize = 4 * BUFFERSIZE + ERR_BUFFERSIZE;
+
+	/* Allocate the primary structure. */
+	struct user *user = (struct user*)malloc(sizeof(struct user));
+	if (!user)
+		return NULL;
+
+	/* Allocate memory for buffer backing buffer. */
+#ifdef USE_TRAP_PAGING
+	allocsize = 4 * round_up(BUFFERSIZE, getpagesize()) +
+		round_up(ERR_BUFFERSIZE, getpagesize()) +
+		6 * getpagesize();
+
+	for (i = 0; i < 5; i++) {
+		offset[i] = i * round_up(BUFFERSIZE, getpagesize()) + (i + 1) * getpagesize();
+		trap[i] = offset[i] - getpagesize();
+	}
+	trap[5] = allocsize - getpagesize();
+
+	user->u_buf_backing_size = allocsize;
+	user->u_buf_backing = (unsigned char*)mmap(NULL,
+		user->u_buf_backing_size,
+		PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+	if (user->u_buf_backing == MAP_FAILED)
+		r = -1;
+	for (i = 0; i < 6; i++)
+		if (mprotect(user->u_buf_backing + trap[i], getpagesize(),
+			PROT_NONE) < 0) {
+			munmap(user->u_buf_backing, user->u_buf_backing_size);
+			r = -1;
+			break;
+		}
+#else
+	user->u_buf_backing = (unsigned char*)malloc(allocsize);
+	user->u_buf_backing_size = 0;
+	if (!user->u_buf_backing)
+		r = -1;
+#endif
+	if (r < 0) {
+		free(user);
+		return NULL;
+	}
+
+	user->u_black_in_eof = 0;
+	user->u_black_out_eof = 0;
+	user->u_black_in_d_eof = 0;
+	user->u_black_out_d_eof = 0;
+	user->u_want_read = 0;
+	user->u_want_write = 0;
+	user->u_want_hand = 0;
+	user->u_red_assume_more = 1;
+	user->u_red_in_have_data = 0;
+	user->u_red_err_hdr_sent = 0;
+	user->u_tls_active = 0;
+	user->u_black_fd = black_fd;
+	user->u_red_in_fd = -1;
+	user->u_red_out_fd = -1;
+	user->u_red_err_fd = -1;
+	user->u_black_in_buf = cbuffer_create(user->u_buf_backing +
+		offset[0], BUFFERSIZE);
+	user->u_black_out_buf = cbuffer_create(user->u_buf_backing +
+		offset[1], BUFFERSIZE);
+	user->u_red_in_buf = cbuffer_create(user->u_buf_backing +
+		offset[2], BUFFERSIZE);
+	user->u_red_out_buf = cbuffer_create(user->u_buf_backing +
+		offset[3], BUFFERSIZE);
+	user->u_red_err_buf = cbuffer_create(user->u_buf_backing +
+		offset[4], ERR_BUFFERSIZE);
+	if (!user->u_black_in_buf || !user->u_black_out_buf ||
+		!user->u_red_in_buf || !user->u_red_out_buf ||
+		!user->u_red_err_buf) {
+		/* Failed to allocate memory. */
+		cbuffer_destroy(user->u_black_in_buf);
+		cbuffer_destroy(user->u_black_out_buf);
+		cbuffer_destroy(user->u_red_in_buf);
+		cbuffer_destroy(user->u_red_out_buf);
+		cbuffer_destroy(user->u_red_err_buf);
+#ifdef USE_TRAP_PAGING
+		munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+		free(user->u_buf_backing);
+#endif
+		free(user);
+		return NULL;
+	}
+	user->u_deadline = t;
+	user->u_failure = USER_STILL_ACTIVE;
+	user->u_delay_failure = USER_STILL_ACTIVE;
+	user->u_errmsg = NULL;
+	user->u_tls_session = NULL;
+	user->u_delay_tls_failure = 0;
+	return user;
+}
+
+
+
+void user_release(struct user *user)
+{
+	if (!user->u_failure)
+		cleanup_user(user, USER_KILL, "User session killed");
+
+	cbuffer_destroy(user->u_black_in_buf);
+	cbuffer_destroy(user->u_black_out_buf);
+	cbuffer_destroy(user->u_red_in_buf);
+	cbuffer_destroy(user->u_red_out_buf);
+	cbuffer_destroy(user->u_red_err_buf);
+	free(user->u_errmsg);
+
+#ifdef USE_TRAP_PAGING
+	munmap(user->u_buf_backing, user->u_buf_backing_size);
+#else
+	free(user->u_buf_backing);
+#endif
+	free(user);
+}
+
+int user_tls_configured(struct user *user)
+{
+	return (user->u_tls_session != NULL);
+}
+
+const char *user_explain_failure(int code)
+{
+	switch(code) {
+	case USER_STILL_ACTIVE:
+		return "Still active";
+	case USER_CONNECTION_END:
+		return "Connection closed";
+	case USER_LAYER4_ERROR:
+		return "Transport error";
+	case USER_TLS_ERROR:
+		return "TLS error";
+	case USER_TLS_HAND_ERROR:
+		return "TLS handshake error";
+	case USER_KILL:
+		return "User killed";
+	case USER_RED_FAILURE:
+		return "Subprocess failure";
+	case USER_TIMEOUT:
+		return "Timeout";
+	default:
+		return "Unknown error";
+	}
+}
+
+struct cbuffer *user_get_red_in_force(struct user *user)
+{
+	return user->u_red_in_buf;
+}
+
+struct cbuffer *user_get_red_out_force(struct user *user)
+{
+	return user->u_red_out_buf;
+}
+
+struct cbuffer *user_get_red_err_force(struct user *user)
+{
+	return user->u_red_err_buf;
+}
+
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert)
+{
+	char error_buffer[8192];
+	if (!user->u_tls_session)
+		return;
+
+	user->u_delay_tls_failure = 1;
+	user->u_delay_alert = alert;
+	sprintf(error_buffer, "TLS alert forced: %s",
+		gnutls_alert_get_name(alert));
+	user->u_errmsg = strdup(error_buffer);
+}
+
+int user_red_out_eofd(struct user *user)
+{
+	if (user->u_red_out_fd >= 0)
+		return 0;
+	if (user->u_tls_session && !user->u_black_in_d_eof)
+		return 0;
+	if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf))
+		return 0;
+	if (!user->u_tls_session && !user->u_black_in_eof)
+		return 0;
+	return 1;
+}
diff --git a/git-over-tls/user.h b/git-over-tls/user.h
new file mode 100644
index 0000000..e921c98
--- /dev/null
+++ b/git-over-tls/user.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _user__h__included__
+#define _user__h__included__
+
+#include <gnutls/gnutls.h>
+#include "cbuffer.h"
+#include <stdint.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminoloyy:
+ *	black:
+ *		Side of user session connected to socket. Transports
+ *		unencrypted or TLS data between peers.
+ *	red:
+ *		Side of user session connected to server loop or to
+ *		helper program.
+ *	red input:
+ *		Input buffer unencrypted data is read from. May be connected
+ *		to file descriptor. In that case that file descriptor is
+ *		read for data.
+ *	red output:
+ *		Output buffer decrypted data is written to. May be connected
+ *		to file descriptor. In that cse that file descriptor is
+ *		written with the data.
+ *	red error:
+ *		Input buffer error input is read from. May be connecte to
+ *		file descriptor, which is read for input data. If red input
+ *		file descriptor has data succesfully read, the red error buffer
+ *		is cleared and any further error input is redirected to bit
+ *		bucket. If red input closes with red error having data, then
+ *		that data is sent as ERR packet when red error has associated
+ *		file descriptor closed.
+ *	red_in:
+ *		File descriptor associated with red input.
+ *	red_out:
+ *		File descriptor associated with red output.
+ *	red_err:
+ *		File descriptor associated with red error.
+ *	deadline:
+ *		Time to disconnect (some) client on or perform some other
+ *		service.
+ */
+
+/* Main session structure. Opaque type. */
+struct user;
+
+/* Failure codes. */
+/* User still active. */
+#define USER_STILL_ACTIVE	0
+/* Connection normal end. */
+#define USER_CONNECTION_END	1
+/* Transport error. */
+#define USER_LAYER4_ERROR	-1
+/* TLS error while not handshaking. */
+#define USER_TLS_ERROR		-2
+/* TLS handshake error. */
+#define USER_TLS_HAND_ERROR	-3
+/* User killed. Never returned as failure code. */
+#define USER_KILL		-4
+/* Red file descriptor I/O operation failure. */
+#define USER_RED_FAILURE	-5
+/* User timed out. */
+#define USER_TIMEOUT		-6
+
+/*
+ * Create new user session.
+ *
+ * Input:
+ *	black_fd	Black fd. Must be socket.
+ *	timeout_secs	Initial timeout in seconds.
+ *
+ * Output:
+ *	Return value	Newly created session, or NULL on out of
+ *			memory.
+ */
+struct user *user_create(int black_fd, unsigned timeout_secs);
+
+/*
+ * Configure session to use TLS. The TLS session must be preconfigured
+ * (credentials set, etc), but not handshaked.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	session		TLS session to assign.
+ */
+void user_configure_tls(struct user *user, gnutls_session_t session);
+
+/*
+ * Add current user session file descriptors that are ready to read or
+ * write to file descriptor sets for read or write. Also update dead-
+ * line if needed.
+ *
+ * Input:
+ *	user		The user session to handle.
+ *	bound		Current file descriptor bound. 0 if there are
+ *			no file descriptors in either set.
+ *	rfds		Read file descriptor set.
+ *	wfds		Write file descriptor set.
+ *	deadline	Current deadline for select.
+ *
+ * Output:
+ *	bound		Updated file descriptor bound.
+ *	rfds		Updated read file descriptor set.
+ *	wfds		Updated write file descriptor set.
+ *	deadline	Updated deadline for select.
+ */
+void user_add_to_sets(struct user *user, int *bound, fd_set *rfds,
+	fd_set *wfds, struct timeval *deadline);
+
+/*
+ * Service this user.
+ *
+ * Input:
+ *	user		The user session to handle.
+ *	rfds		Ready to read file descriptors.
+ *	wfds		Ready to write file descriptors.
+ */
+void user_service(struct user *user, fd_set *rfds, fd_set *wfds);
+
+/*
+ * Service this user without doing any I/O
+ *
+ * Input:
+ *	user		The user session to handle.
+ */
+void user_service_nofd(struct user *user);
+
+/*
+ * Get failure class.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	0 if connection is active, 1 if end
+ *			of connection, negative code if connection
+ *			ended with error. See USER_* defintions.
+ */
+int user_get_failure(struct user *user);
+
+/*
+ * Get explanation of failure code.
+ *
+ * Input:
+ *	code		The failure code.
+ *
+ * Output:
+ *	Return value	String explaining the code. Do not
+ *			free this.
+ */
+const char *user_explain_failure(int code);
+
+/*
+ * Get more detailed error message to explain the failure.
+ *
+ * Input:
+ *	user		The user session.
+ *
+ * Output:
+ *	Return value	Error message or NULL if there is
+ *			no more detailed error message. Do
+ *			not free this.
+ *
+ * Notes:
+ *	- Error message can be NULL or not independently of
+ *	  main failure status.
+ */
+const char *user_get_error(struct user *user);
+
+/*
+ * Has TLS been configured?
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Nonzero if configured, zero if not.
+ */
+/* Returns 1 if TLS has been configured, 0 otherwise. */
+int user_tls_configured(struct user *user);
+
+/*
+ * Return TLS session associated with user session.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If TLS is configured and has handshaked, the TLS
+ *			session. Otherwise NULL.
+ */
+gnutls_session_t user_get_tls(struct user *user);
+
+/*
+ * Free user structure. If user is still active, disconnect user hard.
+ *
+ * Input:
+ *	user		The user session to release.
+ */
+void user_release(struct user *user);
+
+/*
+ * Set red I/O file descriptors.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	red_in		Red input file descriptor, -1 for none.
+ *	red_out		Red output file descritpor, -1 for none.
+ *	red_err		Red error file descriptor, -1 for none.
+ *
+ * Notes:
+ *	- red_in and red_err must be read ends if present.
+ *	- red_out must be write end if present.
+ */
+void user_set_red_io(struct user *user, int red_in, int red_out, int red_err);
+
+/*
+ * Clear red I/O by closing red output and marking no red output file
+ * descriptor. This may be neeeded, since red out can't close without
+ * input to transfer.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ */
+void user_clear_red_io(struct user *user);
+
+/*
+ * Get red input buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_in is set to none, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_in(struct user *user);
+
+/*
+ * Get red output buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_out is set to none, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_out(struct user *user);
+
+/*
+ * Get red error buffer.
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	If red_err is set to none and no data has been
+ *			received through red_in, the buffer, otherwise
+ *			NULL.
+ */
+struct cbuffer *user_get_red_err(struct user *user);
+
+/*
+ * Clear deadline (don't generate timeout anymore).
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ */
+void user_clear_deadline(struct user *user);
+
+/*
+ * Send EOF to red input.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *
+ * Notes:
+ *	- Only works if red input has no file descriptor associated.
+ *	- EOF is automatically sent if red_in encounters EOF.
+ */
+void user_send_red_in_eof(struct user *user);
+
+/*
+ * Force get red input buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red input buffer.
+ */
+struct cbuffer *user_get_red_in_force(struct user *user);
+
+/*
+ * Force get red output buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red output buffer.
+ */
+struct cbuffer *user_get_red_out_force(struct user *user);
+
+/*
+ * Force get red error buffer (even if it shouldn't be available).
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Red error buffer.
+ */
+struct cbuffer *user_get_red_err_force(struct user *user);
+
+/*
+ * Send fatal TLS alert.
+ *
+ * Input:
+ *	user		The user session to manipulate.
+ *	alert		The alert to send.
+ *
+ * Notes:
+ *	- Ignored if no TLS has been configured.
+ */
+void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert);
+
+/*
+ * Has red output been EOF'd?
+ *
+ * Input:
+ *	user		The user session to interrogate.
+ *
+ * Output:
+ *	Return value	Nonzero if red out can receive no more data and
+ *			not connected to file descriptor, otherwise zero
+ *			(more data possible).
+ */
+int user_red_out_eofd(struct user *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
-- 
1.6.6.102.gd6f8f.dirty

^ permalink raw reply related

* [RFC 1/2] Git-over-TLS (gits://) client side support (part 1 of 2)
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
  To: git
In-Reply-To: <1263388786-6880-1-git-send-email-ilari.liusvaara@elisanet.fi>

Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
---
 Makefile                               |   23 ++-
 git-over-tls/.gitignore                |    5 +
 git-over-tls/Makefile                  |   46 +++
 git-over-tls/cbuffer.c                 |  504 ++++++++++++++++++++++++++++++++
 git-over-tls/cbuffer.h                 |  304 +++++++++++++++++++
 git-over-tls/certificate.c             |  306 +++++++++++++++++++
 git-over-tls/certificate.h             |   28 ++
 git-over-tls/connect.c                 |  263 +++++++++++++++++
 git-over-tls/connect.h                 |   14 +
 git-over-tls/genkeypair.c              |   38 +++
 git-over-tls/gensrpverifier.c          |  372 +++++++++++++++++++++++
 git-over-tls/getkeyid.c                |  118 ++++++++
 git-over-tls/gits-send-special-command |   22 ++
 git-over-tls/home.c                    |   47 +++
 git-over-tls/home.h                    |   13 +
 git-over-tls/hostkey.c                 |  116 ++++++++
 git-over-tls/hostkey.h                 |   15 +
 git-over-tls/hostkeymanager.c          |  305 +++++++++++++++++++
 git-over-tls/keypairs.c                |   60 ++++
 git-over-tls/keypairs.h                |   16 +
 20 files changed, 2613 insertions(+), 2 deletions(-)
 create mode 100644 git-over-tls/.gitignore
 create mode 100644 git-over-tls/Makefile
 create mode 100644 git-over-tls/cbuffer.c
 create mode 100644 git-over-tls/cbuffer.h
 create mode 100644 git-over-tls/certificate.c
 create mode 100644 git-over-tls/certificate.h
 create mode 100644 git-over-tls/connect.c
 create mode 100644 git-over-tls/connect.h
 create mode 100644 git-over-tls/genkeypair.c
 create mode 100644 git-over-tls/gensrpverifier.c
 create mode 100644 git-over-tls/getkeyid.c
 create mode 100755 git-over-tls/gits-send-special-command
 create mode 100644 git-over-tls/home.c
 create mode 100644 git-over-tls/home.h
 create mode 100644 git-over-tls/hostkey.c
 create mode 100644 git-over-tls/hostkey.h
 create mode 100644 git-over-tls/hostkeymanager.c
 create mode 100644 git-over-tls/keypairs.c
 create mode 100644 git-over-tls/keypairs.h

diff --git a/Makefile b/Makefile
index 4d2b99c..81cc848 100644
--- a/Makefile
+++ b/Makefile
@@ -163,6 +163,10 @@ all::
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
 #
+# Define NO_GNUTLS if you don't want to use GnuTLS.
+#
+# Define NO_SRP if you don't want SRP support.
+#
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
@@ -171,7 +175,6 @@ all::
 # Define NO_PYTHON if you do not want Python scripts or libraries at all.
 #
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
-#
 # The TCL_PATH variable governs the location of the Tcl interpreter
 # used to optimize git-gui for your system.  Only used if NO_TCLTK
 # is not set.  Defaults to the bare 'tclsh'.
@@ -291,7 +294,7 @@ TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
 
-export TCL_PATH TCLTK_PATH
+export TCL_PATH TCLTK_PATH CC
 
 # sparse is architecture-neutral, which means that we need to tell it
 # explicitly what architecture to check for. Fix this up for yours..
@@ -1248,6 +1251,9 @@ endif
 ifdef NO_IPV6
 	BASIC_CFLAGS += -DNO_IPV6
 endif
+ifdef NO_SRP
+	BASIC_CFLAGS += -DDISABLE_SRP
+endif
 ifdef NO_UINTMAX_T
 	BASIC_CFLAGS += -Duintmax_t=uint32_t
 endif
@@ -1399,6 +1405,10 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
+# In case some subdirectory wants to use git API libs.
+GIT_PROGRAM_LINKLIBS = $(LIBS)
+export GIT_PROGRAM_LINKLIBS
+
 BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
 	$(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
@@ -1442,6 +1452,9 @@ endif
 ifndef NO_PERL
 	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
+ifndef NO_GNUTLS
+	$(QUIET_SUBDIR0)git-over-tls $(QUIET_SUBDIR1) prefix='$(prefix_SQ)' all
+endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
@@ -1825,6 +1838,9 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ifndef NO_GNUTLS
+	$(MAKE) -C git-over-tls DESTDIR_BIN='$(DESTDIR_SQ)$(bindir_SQ)' DESTDIR_GITEXEC='$(DESTDIR_SQ)$(gitexec_instdir_SQ)' install
+endif
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 endif
@@ -1956,6 +1972,9 @@ ifndef NO_PERL
 	$(RM) gitweb/gitweb.cgi
 	$(MAKE) -C perl clean
 endif
+ifndef NO_GNUTLS
+	$(MAKE) -C git-over-tls clean
+endif
 ifndef NO_PYTHON
 	$(MAKE) -C git_remote_helpers clean
 endif
diff --git a/git-over-tls/.gitignore b/git-over-tls/.gitignore
new file mode 100644
index 0000000..546e49c
--- /dev/null
+++ b/git-over-tls/.gitignore
@@ -0,0 +1,5 @@
+/git-remote-gits
+/gits-get-key-id
+/gits-generate-keypair
+/gits-generate-srp-verifier
+/gits-hostkey
diff --git a/git-over-tls/Makefile b/git-over-tls/Makefile
new file mode 100644
index 0000000..7804ec7
--- /dev/null
+++ b/git-over-tls/Makefile
@@ -0,0 +1,46 @@
+GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS))
+helper = git-remote-gits
+programs = gits-get-key-id gits-hostkey gits-generate-keypair
+scripts = gits-send-special-command
+flags=
+
+# These can't be inherited from upper level or they won't work right...
+ifndef V
+	QUIET_CC       = @echo '   ' CC $@;
+	QUIET_LINK     = @echo '   ' LINK $@;
+endif
+
+ifdef NO_SRP
+flags += -DDISABLE_SRP
+else
+programs += gits-generate-srp-verifier
+endif
+
+all: $(programs) $(helper)
+
+git-remote-gits: main.o user.o cbuffer.o srp_askpass.o keypairs.o hostkey.o home.o certificate.o prompt.o misc.o prompt.o connect.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-get-key-id: getkeyid.o certificate.o cbuffer.o home.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+ifndef NO_SRP
+gits-generate-srp-verifier: gensrpverifier.o prompt.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+endif
+
+gits-hostkey: hostkeymanager.o home.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+gits-generate-keypair: genkeypair.o home.o mkcert.o cbuffer.o prompt.o
+	$(QUIET_LINK)$(CC) $(LDFLAGS)  -o $@ $^ $(GITLIBS2) -lgnutls
+
+%.o: %.c
+	$(QUIET_CC)$(CC) $(CLFAGS) -c -o $@ $< $(flags) -I..
+
+install: all
+	$(INSTALL) $(programs) $(scripts) '$(DESTDIR_BIN)'
+	$(INSTALL) $(helper) '$(DESTDIR_GITEXEC)'
+
+clean:
+	$(RM) -f *.o $(programs) $(helper)
diff --git a/git-over-tls/cbuffer.c b/git-over-tls/cbuffer.c
new file mode 100644
index 0000000..e2adec7
--- /dev/null
+++ b/git-over-tls/cbuffer.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "cbuffer.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+#include <sys/uio.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/*
+ * NOTE: This code may not crash, call exit or anything similar unless
+ * program state is corrupt or call parameters are completely invalid.
+ * It also may not call Git APIs.
+ */
+
+struct cbuffer
+{
+	/* Base address for data area. */
+	unsigned char *cb_base;
+	/* Amount of free space. */
+	size_t cb_free;
+	/* Amount of used space. */
+	size_t cb_used;
+	/* Get pointer (relative to base) */
+	size_t cb_get;
+	/* Put pointer (relative to base) */
+	size_t cb_put;
+	/* Total size */
+	size_t cb_size;
+};
+
+/* Assert that invariants of circular buffer hold. */
+static void assert_invariants(struct cbuffer *cbuf)
+{
+	assert(cbuf->cb_free >= 0 && cbuf->cb_free <= cbuf->cb_size);
+	assert(cbuf->cb_used >= 0 && cbuf->cb_used <= cbuf->cb_size);
+	assert(cbuf->cb_free + cbuf->cb_used == cbuf->cb_size);
+	assert(cbuf->cb_get >= 0 && cbuf->cb_get <= cbuf->cb_size);
+	assert(cbuf->cb_put >= 0 && cbuf->cb_put <= cbuf->cb_size);
+	if (cbuf->cb_get == cbuf->cb_put)
+		assert(cbuf->cb_free == 0 || cbuf->cb_used == 0);
+	else if (cbuf->cb_get < cbuf->cb_put)
+		assert(cbuf->cb_get + cbuf->cb_used == cbuf->cb_put);
+	else
+		assert(cbuf->cb_put + cbuf->cb_free == cbuf->cb_get);
+}
+
+/* Ack specified amount of data written. */
+static void ack_write(struct cbuffer *cbuf, size_t wsize)
+{
+	assert_invariants(cbuf);
+	assert(wsize <= cbuf->cb_free);
+	/*
+	 * Writing data decreses free space, increases used space,
+	 * increases put pointer, but it will wrap around if it
+	 * goes past end of buffer.
+	 */
+	cbuf->cb_free -= wsize;
+	cbuf->cb_used += wsize;
+	cbuf->cb_put += wsize;
+	if (cbuf->cb_put >= cbuf->cb_size)
+		cbuf->cb_put -= cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+/* Ack specified amount of data read (removing it). */
+static void ack_read(struct cbuffer *cbuf, size_t rsize)
+{
+	assert_invariants(cbuf);
+	assert(rsize <= cbuf->cb_used);
+	/*
+	 * Reading data decreses used space, increases free space,
+	 * increases get pointer, but it will wrap around if it
+	 * goes past end of buffer.
+	 */
+	cbuf->cb_free += rsize;
+	cbuf->cb_used -= rsize;
+	cbuf->cb_get += rsize;
+	if (cbuf->cb_get >= cbuf->cb_size)
+		cbuf->cb_get -= cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize)
+{
+	struct cbuffer *cbuf = (struct cbuffer*)malloc(sizeof(
+		struct cbuffer));
+	if (cbuf) {
+		cbuf->cb_base = data;
+		cbuf->cb_size = cbuf->cb_free = datasize;
+		cbuf->cb_used = cbuf->cb_get = cbuf->cb_put = 0;
+		assert_invariants(cbuf);
+	}
+	return cbuf;
+}
+
+void cbuffer_destroy(struct cbuffer *cbuf)
+{
+	if (cbuf) {
+		assert_invariants(cbuf);
+		free(cbuf);
+	}
+}
+
+size_t cbuffer_used(struct cbuffer *cbuf)
+{
+	assert_invariants(cbuf);
+	return cbuf->cb_used;
+}
+
+size_t cbuffer_free(struct cbuffer *cbuf)
+{
+	assert_invariants(cbuf);
+	return cbuf->cb_free;
+}
+
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to read too much. */
+	if (toread > cbuf->cb_used)
+		return -1;
+
+	size_t tocopy = toread;
+
+	/*
+	 * Limit the amount of data read to amount fits inside single
+	 * segment. If get pointer is not greater or equal to put pointer,
+	 * then entiere used space is only single segment.
+	 */
+	if (cbuf->cb_get >= cbuf->cb_put)
+		if (tocopy > cbuf->cb_size - cbuf->cb_get)
+			tocopy = cbuf->cb_size - cbuf->cb_get;
+
+	if (tocopy > 0) {
+		/* Copy the segment and mark it read. */
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, tocopy);
+		ack_read(cbuf, tocopy);
+		/* Adjust the request for subsequent segments. */
+		toread -= tocopy;
+		dest += tocopy;
+	}
+
+	/*
+	 * If the read was incomplete, repeat the read request for the
+	 * non-read tail.
+	 */
+	if (toread > 0)
+		return cbuffer_read(cbuf, dest, toread);
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to peek too much. */
+	if (toread > cbuf->cb_used)
+		return -1;
+
+	/* Is the used space as single segment or in two segments? */
+	if (cbuf->cb_get + toread > cbuf->cb_size) {
+		/* Two. We have to compute where segment boundary is and
+		   copy the data as two copies. */
+		size_t firstseg = cbuf->cb_size - cbuf->cb_get;
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, firstseg);
+		memcpy(dest + firstseg, cbuf->cb_base, toread - firstseg);
+	} else {
+		/* One, data can be read as single copy. */
+		memcpy(dest, cbuf->cb_base + cbuf->cb_get, toread);
+	}
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+	size_t towrite)
+{
+	assert_invariants(cbuf);
+
+	/* Check that user doesn't try to write too much. */
+	if (towrite > cbuf->cb_free)
+		return -1;
+
+	size_t tocopy = towrite;
+
+	/*
+	 * Limit the amount of data written to amount fits inside single
+	 * segment. If put pointer is not greater or equal to get pointer,
+	 * then entiere free space is only single segment.
+	 */
+	if (cbuf->cb_put >= cbuf->cb_get)
+		if (tocopy > cbuf->cb_size - cbuf->cb_put)
+			tocopy = cbuf->cb_size - cbuf->cb_put;
+
+	if (tocopy > 0) {
+		/* Copy the segment and mark it written. */
+		memcpy(cbuf->cb_base + cbuf->cb_put, src, tocopy);
+		ack_write(cbuf, tocopy);
+		/* Adjust the request for subsequent segments. */
+		towrite -= tocopy;
+		src += tocopy;
+	}
+
+	/*
+	 * If the write was incomplete, repeat the write request for the
+	 * non-written tail.
+	 */
+	if (towrite > 0)
+		return cbuffer_write(cbuf, src, towrite);
+
+	assert_invariants(cbuf);
+
+	return 0;
+}
+
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove)
+{
+	assert_invariants(dest);
+	assert_invariants(src);
+
+	/* Check that amount to move isn't too great. */
+	if (tomove > dest->cb_free)
+		return -1;
+	if (tomove > src->cb_used)
+		return -1;
+
+	size_t tocopy = tomove;
+	/*
+	 * Compute maximum number of bytes that is less than amount to
+	 * move and amount of used/free space in current segments in
+	 * both buffers.
+	 */
+	if (dest->cb_put >= dest->cb_get)
+		if (tocopy > dest->cb_size - dest->cb_put)
+			tocopy = dest->cb_size - dest->cb_put;
+	if (src->cb_get >= src->cb_put)
+		if (tocopy > src->cb_size - src->cb_get)
+			tocopy = src->cb_size - src->cb_get;
+
+	if (tocopy > 0) {
+		/* Move the segment. and mark it moved. */
+		memcpy(dest->cb_base + dest->cb_put,
+			src->cb_base +src->cb_get, tocopy);
+		ack_read(src, tocopy);
+		ack_write(dest, tocopy);
+		/* Adjust request for subsequent segments. */
+		tomove -= tocopy;
+	}
+
+	/* If request was incomplete, move the yet unmoved tail. */
+	if (tomove > 0)
+		return cbuffer_move(dest, src, tomove);
+
+	assert_invariants(dest);
+	assert_invariants(src);
+
+	return 0;
+}
+
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd)
+{
+	ssize_t r;
+
+	assert_invariants(cbuf);
+
+	/* Generate EAGAIN if needed (on no free space). */
+	if (cbuf->cb_free == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	/*
+	 * If scatter-gather I/O is available, entiere buffer may be read at
+	 * once. Otherwise only single segment can be read at time.
+	 */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+	struct iovec areas[2];
+	int touse;
+
+	/* One or two segments? */
+	if (cbuf->cb_put >= cbuf->cb_get) {
+		/* Two. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+		areas[0].iov_len = cbuf->cb_size - cbuf->cb_put;
+		areas[1].iov_base = cbuf->cb_base;
+		areas[1].iov_len = cbuf->cb_get;
+		touse = 2;
+	} else {
+		/* One. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_put;
+		areas[0].iov_len = cbuf->cb_get - cbuf->cb_put;
+		touse = 1;
+	}
+	r = readv(fd, areas, touse);
+#else
+	/* Read into current segment. */
+	if (cbuf->cb_put >= cbuf->cb_get)
+		r = read(fd, cbuf->cb_base + cbuf->cb_put,
+			cbuf->cb_size - cbuf->cb_put);
+	else
+		r = read(fd, cbuf->cb_base + cbuf->cb_put,
+			cbuf->cb_get - cbuf->cb_put);
+#endif
+	/* Ack any successfully read data as written. */
+	if (r > 0)
+		ack_write(cbuf, (size_t)r);
+
+	assert_invariants(cbuf);
+
+	return r;
+}
+
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd)
+{
+	ssize_t r;
+
+	assert_invariants(cbuf);
+
+	/* Generate EAGAIN if needed (on no used space). */
+	if (cbuf->cb_used == 0) {
+		errno = EAGAIN;
+		return -1;
+	}
+
+	/*
+	 * If scatter-gather I/O is available, entiere buffer may be written
+	 * at once. Otherwise only single segment can be written at time.
+	 */
+#ifdef USE_UNIX_SCATTER_GATHER_IO
+	struct iovec areas[2];
+	int touse;
+
+	/* One or two segments? */
+	if (cbuf->cb_get >= cbuf->cb_put) {
+		/* Two. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+		areas[0].iov_len = cbuf->cb_size - cbuf->cb_get;
+		areas[1].iov_base = cbuf->cb_base;
+		areas[1].iov_len = cbuf->cb_put;
+		touse = 2;
+	} else {
+		/* One. */
+		areas[0].iov_base = cbuf->cb_base + cbuf->cb_get;
+		areas[0].iov_len = cbuf->cb_put - cbuf->cb_get;
+		touse = 1;
+	}
+	r = writev(fd, areas, touse);
+#else
+	/* Write current segment. */
+	if (cbuf->cb_get >= cbuf->cb_put)
+		r = write(fd, cbuf->cb_base + cbuf->cb_get,
+			cbuf->cb_size - cbuf->cb_get);
+	else
+		r = write(fd, cbuf->cb_base + cbuf->cb_get,
+			cbuf->cb_put - cbuf->cb_get);
+#endif
+	/* Ack any successfully written data as read. */
+	if (r > 0)
+		ack_read(cbuf, (size_t)r);
+
+	assert_invariants(cbuf);
+
+	return r;
+}
+
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length)
+{
+	assert_invariants(cbuf);
+
+	/* Compute segment base. */
+	*base = cbuf->cb_base + cbuf->cb_get;
+
+	if (!cbuf->cb_used) {
+		/* No used space -> empty segment. */
+		*length = 0;
+	} else if (cbuf->cb_get >= cbuf->cb_put) {
+		/* High segment is current. */
+		*length = cbuf->cb_size - cbuf->cb_get;
+	} else {
+		/* Low segment is current. */
+		*length = cbuf->cb_put - cbuf->cb_get;
+	}
+
+	assert_invariants(cbuf);
+}
+
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length)
+{
+	assert_invariants(cbuf);
+	/*
+	 * This doesn't handle read being longer than single segment right,
+	 * but that's undefined anyway.
+	 */
+	ack_read(cbuf, length);
+	assert_invariants(cbuf);
+}
+
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length)
+{
+	assert_invariants(cbuf);
+
+	/* Compute segment base. */
+	*base = cbuf->cb_base + cbuf->cb_put;
+
+	if (!cbuf->cb_free) {
+		/* No free space -> empty segment. */
+		*length = 0;
+	} else if (cbuf->cb_put >= cbuf->cb_get) {
+		/* High segment is current. */
+		*length = cbuf->cb_size - cbuf->cb_put;
+	} else {
+		/* Low segment is current. */
+		*length = cbuf->cb_get - cbuf->cb_put;
+	}
+
+	assert_invariants(cbuf);
+}
+
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length)
+{
+	assert_invariants(cbuf);
+	/*
+	 * This doesn't handle write being longer than single segment right,
+	 * but that's undefined anyway.
+	 */
+	ack_write(cbuf, length);
+	assert_invariants(cbuf);
+}
+
+
+void cbuffer_clear(struct cbuffer *cbuf)
+{
+	/*
+	 * Just resetting pointers and values to initial defaults clears
+	 * all data.
+	 */
+	cbuf->cb_used = cbuf->cb_put = cbuf->cb_get = 0;
+	cbuf->cb_free = cbuf->cb_size;
+	assert_invariants(cbuf);
+}
+
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do read request. */
+	if (limit > cbuffer_used(cbuf))
+		limit = cbuffer_used(cbuf);
+	cbuffer_read(cbuf, dest, limit);
+	return limit;
+}
+
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do write request. */
+	if (limit > cbuffer_free(cbuf))
+		limit = cbuffer_free(cbuf);
+	cbuffer_write(cbuf, src, limit);
+	return limit;
+}
+
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+	size_t limit)
+{
+	/* Limit the request to maximum possible and do move request. */
+	if (limit > cbuffer_free(dest))
+		limit = cbuffer_free(dest);
+	if (limit > cbuffer_used(src))
+		limit = cbuffer_used(src);
+	cbuffer_move(dest, src, limit);
+	return limit;
+}
+
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src)
+{
+	size_t limit;
+	/*
+	 * Limit the request to maximum possible and do move request.
+	 * The move lacks limit so use free space in destination as
+	 * first limit.
+	 */
+	limit = cbuffer_free(dest);
+	if (limit > cbuffer_used(src))
+		limit = cbuffer_used(src);
+	cbuffer_move(dest, src, limit);
+	return limit;
+}
diff --git a/git-over-tls/cbuffer.h b/git-over-tls/cbuffer.h
new file mode 100644
index 0000000..4e07c5b
--- /dev/null
+++ b/git-over-tls/cbuffer.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _cbuffer__h__included__
+#define _cbuffer__h__included__
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Terminology:
+ *	Segment:
+ *		Memory-contigious range extending from get pointer, put
+ *		pointer or start of circular buffer till used/free type
+ *		changes.
+ *	Current segment:
+ *		Segment starting from get pointer or put pointer.
+ *
+ */
+
+/* Primary circular buffer structure. Opaque type. */
+struct cbuffer;
+
+/*
+ * Create new circular buffer using specified data area. The data area
+ * is not copied and must remain until circular buffer is destroyed.
+ *
+ * Inputs:
+ *	data		The data area to back the circular buffer.
+ *	datasize	Size of backing data area.
+ *
+ * Outputs:
+ *	return value	The newly created circular buffer, or NULL
+ *			if out of memory.
+ */
+struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize);
+
+
+/*
+ * Free circular buffer. Data area is not freed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to free.
+ */
+void cbuffer_destroy(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes used in buffer (how many bytes can be read without
+ * writes).
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to interrogate.
+ *
+ * Outputs:
+ *	return value	Number of bytes data in buffer.
+ */
+size_t cbuffer_used(struct cbuffer *cbuf);
+
+/*
+ * Return number of bytes free in buffer (how many bytes can be written
+ * without reads).
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to interrogate.
+ *
+ * Outputs:
+ *	return value	Number of bytes free in buffer.
+ */
+size_t cbuffer_free(struct cbuffer *cbuf);
+
+/*
+ * Peek specified number of bytes from buffer. The bytes are not removed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to peek.
+ *	dest		Destination buffer to store the peeked data to.
+ *	toread		Number of bytes to peek.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			amount of data.
+ *
+ */
+int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Read specified number of bytes from buffer. The bytes read are
+ * removed.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to read.
+ *	dest		Destination buffer to store the read data to.
+ *	toread		Number of bytes to read.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			amount of data.
+ */
+int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread);
+
+/*
+ * Write specified number of bytes to buffer.
+ *
+ * Inputs:
+ *	cbuf		The circular buffer to write.
+ *	src		Buffer to read the written data from.
+ *	towrite		Number of bytes to write.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if buffer has insufficient
+ *			free space.
+ */
+int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src,
+	size_t towrite);
+
+/*
+ * Move specified number of bytes from buffer to another. The data is
+ * removed from source buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source cirrcular buffer.
+ *	tomove		Number of bytes to move.
+ *
+ * Outputs:
+ *	Return value	0 on success, -1 if insufficient source buffer
+ *			data or insufficient destination buffer space.
+ */
+int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove);
+
+/*
+ * Call read on file descriptor. The call tries to read as much as
+ * possible in one go (up to entiere free space of destination
+ * buffer).
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to store the data to.
+ *	fd		File descriptor to read.
+ *
+ * Outputs:
+ *	Return value	Number of bytes read (>0) on success, 0 if
+ *			EOF on file descriptor or -1 if read failed.
+ *	errno		Set by read() or readv() on failure.
+ *			EAGAIN if there is no free space in circular buffer.
+ */
+ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Call write on file descriptor. The call tries to write as much as
+ * possible in one go (up to entiere used space of soruce
+ * buffer).
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read data from.
+ *	fd		File descriptor to write.
+ *
+ * Outputs:
+ *	Return value	Number of bytes written (>=0) on success,
+ *			-1 if write failed.
+ *	errno		Set by write() or writev() on failure.
+ *			EAGAIN if there is no used space in circular buffer.
+ */
+ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd);
+
+/*
+ * Fill buffer read segment for direct from memory read operation on
+ * circular buffer. Any read operation on buffer invalidates segment.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read from.
+ *
+ * Outputs:
+ *	base		Start address of segemnt is written here.
+ *	length		Length of segment is written here.
+ *
+ * Notes:
+ *	- Returned length of 0 is only possible if there is no used
+ *	  space in buffer.
+ *	- The entiere used space may not be returned in single segment.
+ */
+void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length);
+
+/*
+ * Commit read segment after direct read operation, marking data as
+ * read. The read bytes are removed.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer read from.
+ *	length		Length of segment to mark as read. Must
+ *			be at most the length gotten from
+ *			cbuffer_fill_r_segment.
+ */
+void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Fill buffer write segment for direct to memory write operation on
+ * circular buffer. Any write operation on buffer invalidates segment.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to write to.
+ *
+ * Outputs:
+ *	base		Start address of segment is written here.
+ *	length		Length of segment is written here.
+ *
+ * Notes:
+ *	- Returned length of 0 is only possible if there is no free
+ *	  space in buffer.
+ *	- The entiere free space may not be returned in single segment.
+ */
+void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base,
+	size_t *length);
+
+/*
+ * Commit write segment after direct write operation, marking data as
+ * written
+ *
+ * Inputs:
+ *	cbuf		Circular buffer written to.
+ *	length		Length of segment to mark as written. Must
+ *			be at most the length gotten from
+ *			cbuffer_fill_w_segment.
+ */
+void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length);
+
+/*
+ * Clear all data in cbuffer, freeing any used space.
+ *
+ * Inputs:
+ *	cbuf		Cirecular buffer to clear.
+ */
+void cbuffer_clear(struct cbuffer *cbuf);
+
+/*
+ * Read number of bytes from circular buffer smaller than limit.
+ * The read bytes are removed.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to read.
+ *	dest		Destination to store the read data.
+ *	limit		Maximum number of bytes to read.
+ *
+ * Outputs:
+ *	Return value	Number of bytes read.
+ */
+size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest,
+	size_t limit);
+
+/*
+ * Write number of bytes to circular buffer smaller than limit.
+ *
+ * Inputs:
+ *	cbuf		Circular buffer to write.
+ *	dest		Source to read the wrritten data.
+ *	limit		Maximum number of bytes to write.
+ *
+ * Outputs:
+ *	Return value	Number of bytes written.
+ */
+size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src,
+	size_t limit);
+
+/*
+ * Move number of bytes from circular buffer to another smaller than limit.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source circular buffer.
+ *	limit		Maximum number of bytes to move.
+ *
+ * Outputs:
+ *	Return value	Number of bytes moved.
+ */
+size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src,
+	size_t limit);
+
+/*
+ * Move as much bytes from circular buffer to another as possible.
+ * The read bytes are removed from source circular buffer.
+ *
+ * Inputs:
+ *	dest		Destination circular buffer.
+ *	src		Source circular buffer.
+ *
+ * Outputs:
+ *	Return value	Number of bytes moved.
+ */
+size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/git-over-tls/certificate.c b/git-over-tls/certificate.c
new file mode 100644
index 0000000..adeb776
--- /dev/null
+++ b/git-over-tls/certificate.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "cbuffer.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#include "run-command.h"
+#endif
+
+#define CERT_MAX 65536
+
+static long read_short(struct cbuffer *buffer)
+{
+	unsigned char x[2];
+	if (cbuffer_read(buffer, x, 2) < 0)
+		return -1;
+
+	return ((long)x[0] << 8) | ((long)x[1]);
+}
+
+
+static int unseal_cert(struct cbuffer *sealed, struct cbuffer *unsealed,
+	const char *unsealer)
+{
+	struct child_process child;
+	char **argv;
+	char *unsealer_copy;
+	int splits = 0;
+	int escape = 0;
+	int ridx, widx, tidx;
+	const char *i;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	for (i = unsealer; *i; i++) {
+		if (escape)
+			escape = 0;
+		else if (*i == '\\')
+			escape = 1;
+		else if (*i == ' ')
+			splits++;
+	}
+
+	argv = xmalloc((splits + 2) * sizeof(char*));
+	argv[splits + 1] = NULL;
+
+	unsealer_copy = xstrdup(unsealer);
+	argv[0] = unsealer_copy;
+
+	ridx = 0;
+	widx = 0;
+	tidx = 1;
+	escape = 0;
+	while (unsealer_copy[ridx]) {
+		if (escape) {
+			escape = 0;
+			unsealer_copy[widx++] = unsealer_copy[ridx++];
+		} else if (unsealer_copy[ridx] == '\\') {
+			ridx++;
+			escape = 1;
+		} else if (unsealer_copy[ridx] == ' ') {
+			unsealer_copy[widx++] = '\0';
+			argv[tidx++] = unsealer_copy + widx;
+			ridx++;
+		} else
+			unsealer_copy[widx++] = unsealer_copy[ridx++];
+	}
+	unsealer_copy[widx] = '\0';
+
+	memset(&child, 0, sizeof(child));
+	child.argv = (const char**)argv;
+	child.in = -1;
+	child.out = -1;
+	child.err = 0;
+	if (start_command(&child))
+		die("Running keypair unsealer command failed");
+
+	while (1) {
+		int bound;
+		fd_set rf;
+		fd_set wf;
+		int r;
+
+		FD_ZERO(&rf);
+		FD_ZERO(&wf);
+		FD_SET(child.out, &rf);
+		if (cbuffer_used(sealed))
+			FD_SET(child.in, &wf);
+		else
+			close(child.in);
+
+		if (cbuffer_used(sealed))
+			bound = ((child.out > child.in) ? child.out :
+				child.in) + 1;
+		else
+			bound = child.out + 1;
+
+		r = select(bound, &rf, &wf, NULL, NULL);
+		if (r < 0 && r != EINTR)
+			die_errno("Select failed");
+		if (r < 0) {
+			FD_ZERO(&rf);
+			FD_ZERO(&wf);
+			perror("select");
+		}
+
+		if (FD_ISSET(child.out, &rf)) {
+			r = cbuffer_read_fd(unsealed, child.out);
+			if (r < 0 && errno != EINTR && errno != EAGAIN)
+				die_errno("Read from unsealer failed");
+			if (r < 0 && errno == EAGAIN)
+				if (!cbuffer_free(unsealed))
+					die("Keypair too big");
+			if (r < 0)
+				perror("read");
+			if (r == 0)
+				break;
+		}
+
+		if (FD_ISSET(child.in, &wf)) {
+			r = cbuffer_write_fd(sealed, child.in);
+			if (r < 0 && errno == EPIPE)
+				die("Unsealer exited unexpectedly");
+			if (r < 0 && errno != EINTR && errno != EAGAIN)
+				die_errno("Write to unsealer failed");
+			if (r < 0)
+				perror("write");
+		}
+	}
+
+	if (finish_command(&child))
+		die("Keypair unsealer command failed");
+
+	return 0;
+}
+
+
+struct certificate parse_certificate(const char *name, int *errorcode)
+{
+	struct cbuffer *sealed = NULL;
+	struct cbuffer *unsealed = NULL;
+	unsigned char sealed_buf[CERT_MAX];
+	unsigned char unsealed_buf[CERT_MAX];
+	struct certificate cert;
+	int fd;
+	unsigned char head[10];
+	long tmp;
+
+	*errorcode = CERTERR_OK;
+	cert.public_key.data = NULL;
+	cert.public_key.size = 0;
+	cert.private_key.data = NULL;
+	cert.private_key.size = 0;
+
+	sealed = cbuffer_create(sealed_buf, CERT_MAX);
+	if (!sealed)
+		die("Ran out of memory");
+
+	unsealed = cbuffer_create(unsealed_buf, CERT_MAX);
+	if (!unsealed)
+		die("Ran out of memory");
+
+	fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT)
+			*errorcode = CERTERR_NOCERT;
+		else
+			*errorcode = CERTERR_CANTREAD;
+		goto out_unsealed;
+	}
+
+	while (1) {
+		ssize_t r = cbuffer_read_fd(sealed, fd);
+		if (r == 0)
+			break;
+		if (r < 0 && errno == EAGAIN) {
+			if (!cbuffer_free(sealed)) {
+				*errorcode = CERTERR_TOOBIG;
+				goto out_close;
+			}
+		} else if (r < 0 && errno != EINTR) {
+			*errorcode = CERTERR_CANTREAD;
+			goto out_close;
+		}
+	}
+
+	head[9] = 0;
+	if (cbuffer_read(sealed, head, 9) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	if (strcmp((char*)head, "GITSSCERT") &&
+		strcmp((char*)head, "GITSUCERT")) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+
+	if (!strcmp((char*)head, "GITSSCERT")) {
+		/* Sealed certificate. */
+		char *unsealer;
+		int s;
+		tmp = read_short(sealed);
+		if (tmp <= 0) {
+			*errorcode = CERTERR_INVALID;
+			goto out_close;
+		}
+		unsealer = xmalloc(tmp + 1);
+		unsealer[tmp] = '\0';
+		if (cbuffer_read(sealed, (unsigned char*)unsealer, tmp) < 0) {
+			free(unsealer);
+			*errorcode = CERTERR_INVALID;
+			goto out_close;
+		}
+		s = unseal_cert(sealed, unsealed, unsealer);
+		free(unsealer);
+		if (s < 0) {
+			*errorcode = s;
+			goto out_close;
+		}
+	} else {
+		/* Unsealed certificate. */
+		cbuffer_move_nolimit(unsealed, sealed);
+	}
+
+	cert.private_key.data = NULL;
+	cert.public_key.data = NULL;
+
+	tmp = read_short(unsealed);
+	if (tmp < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	cert.private_key.size = tmp;
+	cert.private_key.data = (unsigned char*)gnutls_malloc(tmp);
+	if (!cert.private_key.data)
+		die("Ran out of memory");
+
+	if (cbuffer_read(unsealed, cert.private_key.data,
+		cert.private_key.size) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_private;
+	}
+
+	tmp = read_short(unsealed);
+	if (tmp < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_close;
+	}
+	cert.public_key.size = tmp;
+	cert.public_key.data = (unsigned char*)gnutls_malloc(tmp);
+	if (!cert.public_key.data)
+		die("Ran out of memory");
+
+	if (cbuffer_read(unsealed, cert.public_key.data,
+		cert.public_key.size) < 0) {
+		*errorcode = CERTERR_INVALID;
+		goto out_public;
+	}
+
+	if (cbuffer_used(unsealed)) {
+		*errorcode = CERTERR_INVALID;
+		goto out_public;
+	}
+
+	goto out_close;
+
+out_public:
+	gnutls_free(cert.private_key.data);
+out_private:
+	gnutls_free(cert.private_key.data);
+out_close:
+	close(fd);
+out_unsealed:
+	cbuffer_destroy(unsealed);
+	cbuffer_destroy(sealed);
+	return cert;
+}
+
+const char *cert_parse_strerr(int errcode)
+{
+	switch(errcode) {
+	case CERTERR_OK:
+		return "Success";
+	case CERTERR_NOCERT:
+		return "No such keypair";
+	case CERTERR_INVALID:
+		return "Keypair file corrupt";
+	case CERTERR_CANTREAD:
+		return "Can't read keypair file";
+	case CERTERR_TOOBIG:
+		return "Keypair too big";
+	}
+	return "<Unknown error>";
+}
diff --git a/git-over-tls/certificate.h b/git-over-tls/certificate.h
new file mode 100644
index 0000000..5ee355a
--- /dev/null
+++ b/git-over-tls/certificate.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _certificate__h__included__
+#define _certificate__h__included__
+
+#include <gnutls/gnutls.h>
+
+#define CERTERR_OK		0
+#define CERTERR_NOCERT		-2
+#define CERTERR_INVALID		-3
+#define CERTERR_CANTREAD	-4
+#define CERTERR_TOOBIG		-5
+
+struct certificate
+{
+	gnutls_datum_t public_key;
+	gnutls_datum_t private_key;
+};
+
+struct certificate parse_certificate(const char *name, int *errorcode);
+const char *cert_parse_strerr(int errcode);
+
+#endif
diff --git a/git-over-tls/connect.c b/git-over-tls/connect.c
new file mode 100644
index 0000000..8f19bc8
--- /dev/null
+++ b/git-over-tls/connect.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "connect.h"
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifndef WIN32
+#include <sys/un.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+static int connect_unix(const char* path)
+{
+#ifndef WIN32
+	struct sockaddr_un saddru;
+	int fd, ret, plen;
+
+	if (strlen(path) > UNIX_PATH_MAX - 1)
+		die("Unix socket path too long");
+
+	saddru.sun_family = AF_UNIX;
+	strcpy(saddru.sun_path, path);
+	if (*saddru.sun_path == '@')
+		*saddru.sun_path = '\0';
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (fd < 0)
+		die_errno("Can't create socket");
+	if (*path == '@')
+		plen = (int)((char*)saddru.sun_path - (char*)&saddru) +
+			strlen(path);
+	else
+		plen = (int)sizeof(saddru);
+	ret = connect(fd, (struct sockaddr*)&saddru, plen);
+	if (ret < 0) {
+		die_errno("Can't connect to %s", path);
+	}
+	return fd;
+#else
+	die("Unix domain sockets not supported by this build");
+#endif
+}
+
+static int connect_address(const char* host, unsigned short port,
+	int protocol, struct sockaddr* addr, int size)
+{
+	char address[1024];
+	int fd, ret;
+	const unsigned char* _addr;
+
+	fd = socket(addr->sa_family, SOCK_STREAM, protocol);
+	if (fd < 0) {
+		error("Can't create socket: %s", strerror(errno));
+		return -1;
+	}
+	switch(addr->sa_family) {
+	case AF_INET:
+		_addr = (const unsigned char*)
+			&(((struct sockaddr_in*)addr)->sin_addr.s_addr);
+		sprintf(address, "%u.%u.%u.%u", _addr[0], _addr[1],
+			_addr[2], _addr[3]);
+		break;
+#ifndef NO_IPV6
+	case AF_INET6:
+		_addr = (const unsigned char*)
+			((struct sockaddr_in6*)addr)->sin6_addr.s6_addr;
+		sprintf(address, "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+			"%02X%02X:%02X%02X:%02X%02X:%02X%02X",
+			_addr[0], _addr[1], _addr[2], _addr[3],
+			_addr[4], _addr[5], _addr[6], _addr[7],
+			_addr[8], _addr[9], _addr[10], _addr[11],
+			_addr[12], _addr[13], _addr[14], _addr[15]);
+		break;
+#endif
+	default:
+		sprintf(address, "<unknown address type>");
+		break;
+	}
+	ret = connect(fd, addr, size);
+	if (ret < 0) {
+		error("Can't connect to host %s[%s] port %u: %s",
+			host, address, port, strerror(errno));
+		return -1;
+	}
+	return fd;
+}
+
+int connect_gethostbyname(const char* _host,
+	unsigned short _port, uint32_t scope)
+{
+#ifndef NO_IPV6
+	struct addrinfo hints;
+	struct addrinfo* returned;
+	char port[10];
+	int fd, ret;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_STREAM;
+
+	sprintf(port, "%u", _port);
+	ret = getaddrinfo(_host, port, &hints, &returned);
+	if (ret)
+		die("getaddrinfo(%s, %s, ...): %s", _host, port,
+			gai_strerror(ret));
+
+	while (returned) {
+		if (returned->ai_family == AF_INET6)
+			((struct sockaddr_in6*)returned->ai_addr)->
+				sin6_scope_id = scope;
+		else if (scope)
+			warning("Scope is ignored for non-IPv6 addresses");
+
+		fd = connect_address(_host, _port, returned->ai_protocol,
+			returned->ai_addr, returned->ai_addrlen);
+
+		if (fd >= 0)
+			goto out;
+		returned = returned->ai_next;
+	}
+
+	die("Can't connect to host %s", _host);
+
+out:
+	freeaddrinfo(returned);
+	return fd;
+#else
+	struct hostent *host;
+	int fd;
+	static struct sockaddr_in saddr4;
+
+	host = gethostbyname(_host);
+	if (!host || !host->h_addr)
+		die("Can't find host %s", _host);
+
+next_address:
+	if (host->h_addrtype == AF_INET) {
+		memset(&saddr4, 0, sizeof(saddr4));
+		saddr4.sin_family = AF_INET;
+		memcpy(&saddr4.sin_addr, host->h_addr_list[0], 4);
+		saddr4.sin_port = htons(_port);
+		fd = connect_address(_host, _port, 0,
+			(struct sockaddr*)&saddr4,
+			sizeof(struct sockaddr_in));
+		if (scope)
+			warning("Scope is ignored for non-IPv6 addresses");
+	} else
+		die("Host %s has unknown address type", _host);
+
+	if (fd >= 0)
+		goto out;
+
+	host->h_addr_list++;
+	if (host->h_addr_list)
+		goto next_address;
+
+	if (scope > 0)
+		die("Can't connect to host %s%%u", _host, scope);
+	else
+		die("Can't connect to host %s", _host);
+out:
+	return fd;
+#endif
+}
+
+/* Parse character as base-10 digit. */
+static int char_to_int(char ch)
+{
+	switch(ch) {
+	case '0':
+		return 0;
+	case '1':
+		return 1;
+	case '2':
+		return 2;
+	case '3':
+		return 3;
+	case '4':
+		return 4;
+	case '5':
+		return 5;
+	case '6':
+		return 6;
+	case '7':
+		return 7;
+	case '8':
+		return 8;
+	case '9':
+		return 9;
+	default:
+		return -1;
+	}
+}
+
+static uint32_t touint32_t(const char* num)
+{
+	uint32_t x = 0;
+	unsigned i;
+
+	/* Blank string is not valid number. */
+	if (!*num)
+		die("Invalid scope id '%s'", num);
+
+	/* 0 is special case, makes it easier to deal with zeros. */
+	if (!strcmp(num, "0"))
+		return 0;
+
+	/*
+	 * Valid uints numbers are 0-2^32-1, but because we handled 0 as
+	 * special case, the valid range can be 1-2^32-1 here.
+	 */
+	for (i = 0; num[i]; i++) {
+		char ch = char_to_int(num[i]);
+		if (ch < 0)
+			die("Invalid scope id '%s'", num);
+		if (ch == 0 && x == 0)
+			die("Invalid scope id '%s'", num);
+		if (x > 429496729 || (x > 429496728 && ch > 5))
+			die("Invalid scope id '%s'", num);
+		x = x * 10 + ch;
+	}
+	return x;
+}
+
+int connect_host(const char* _host, unsigned short _port)
+{
+	uint32_t scope = 0;
+	char* scopestart;
+	char* hostcopy;
+
+	if (_host[0] == '/' || (_host[0] == '@' && _host[1] == '/'))
+		return connect_unix(_host);
+
+	hostcopy = xmalloc(strlen(_host) + 1);
+	strcpy(hostcopy, _host);
+	scopestart = strchr(hostcopy, '%');
+	if (scopestart) {
+		*(scopestart++) = '\0';
+		scope = touint32_t(scopestart);
+	}
+
+	return connect_gethostbyname(hostcopy, _port, scope);
+}
diff --git a/git-over-tls/connect.h b/git-over-tls/connect.h
new file mode 100644
index 0000000..90e36cd
--- /dev/null
+++ b/git-over-tls/connect.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _connect__h__included__
+#define _connect__h__included__
+
+//Returns connected fd or dies.
+int connect_host(const char* host, unsigned short port);
+
+#endif
diff --git a/git-over-tls/genkeypair.c b/git-over-tls/genkeypair.c
new file mode 100644
index 0000000..4f2142c
--- /dev/null
+++ b/git-over-tls/genkeypair.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void write_cert(int server_mode);
+
+static void do_help()
+{
+	printf("gits-generate-keypair: User keypair generator.\n");
+	printf("Command line options:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("\n");
+	printf("Note: Keep generated keypair files private!\n");
+	printf("Use gits-get-key-name to get public short\n");
+	printf("representation of keypair for authorization.\n");
+	printf("\n");
+	printf("WARNING: Don't let keypairs to be tampered with!\n");
+	printf("WARNING: Tampered keypairs may do very nasty things\n");
+	printf("WARNING: if used.\n");
+	exit(0);
+}
+
+
+int main(int argc, char **argv)
+{
+	if (argc > 1 && !strcmp(argv[1], "--help"))
+		do_help();
+	write_cert(0);
+	return 0;
+}
diff --git a/git-over-tls/gensrpverifier.c b/git-over-tls/gensrpverifier.c
new file mode 100644
index 0000000..751854a
--- /dev/null
+++ b/git-over-tls/gensrpverifier.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "prompt.h"
+#include <stdio.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+static void do_help()
+{
+	printf("gits-generate-srp-verifier: Generate SRP verifiers for\n");
+	printf("password authentication\n");
+	printf("Command line:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("\n");
+	printf("Note: Server needs SRP verifier in order to do password\n");
+	printf("authentication. Usernames are assigned by repostiory\n");
+	printf("hosting admin, just using arbitrary names doesn't work.\n");
+	exit(0);
+}
+
+
+struct field
+{
+	const char *f_name;
+	const char *f_generator;
+	const char *f_prime;
+};
+
+struct field fields[] = {
+	{
+	"standard 1024 bit field", "2",
+	"Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE"
+	"3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Z"
+	"jl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ"
+	},
+	{
+	"standard 1536 bit field", "2",
+	"dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5"
+	"LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTa"
+	"u/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtm"
+	"cIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx"
+	},
+	{
+	"standard 2048 bit field", "2",
+	"2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJt"
+	"L8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL"
+	".NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nq"
+	"ha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXq"
+	"vp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUD"
+	"o/DQEyW.d4H.UIlzp"
+	},
+	{NULL, NULL, NULL}
+};
+
+
+unsigned char xfact[16] = {
+0x9D, 0x0F, 0x49, 0xD4,
+0x73, 0x88, 0xC7, 0xFF,
+0xFD, 0x24, 0x6A, 0x0F,
+0x94, 0x78, 0x0C, 0x14
+};
+
+unsigned char rnd[16] = {
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00,
+0x00, 0x00, 0x00, 0x00
+};
+
+static void add(unsigned char *res, unsigned char *a, unsigned char *b)
+{
+	unsigned char carry = 0;
+	unsigned i;
+
+	for (i = 0; i < 16; i++) {
+		unsigned char newcarry = 0;
+
+		if ((unsigned char)(a[i] + b[i]) < a[i])
+			newcarry++;
+		res[i] = a[i] + b[i];
+		if (res[i] + carry < res[i])
+			newcarry++;
+		res[i] += carry;
+		carry = newcarry;
+	}
+	while (carry) {
+		carry = 159;
+
+		for (i = 0; i < 16; i++) {
+			int newcarry = 0;
+
+			if ((unsigned char)(res[i] + carry) < res[i])
+				newcarry++;
+			res[i] += carry;
+			carry = newcarry;
+		}
+	}
+	for (i = 1; i < 16; i++)
+		if (res[i] < 255)
+			goto skip;
+
+	if (res[0] > 0x60) {
+		for (i = 1; i < 16; i++)
+			res[i] = 0;
+		res[0] -= 0x61;
+	}
+skip:
+	;
+}
+
+void update_xfact()
+{
+	unsigned char xfact2[16];
+	unsigned char xfact4[16];
+	unsigned char xfact5[16];
+	add(xfact2, xfact, xfact);
+	add(xfact4, xfact2, xfact2);
+	add(xfact5, xfact4, xfact);
+	memcpy(xfact, xfact5, 16);
+}
+
+void update_rnd(unsigned char ch)
+{
+	unsigned char rndt[16];
+	unsigned i;
+	for (i = 0; i < 8; i++) {
+		if ((ch >> i) % 2) {
+			add(rndt, rnd, xfact);
+			memcpy(rnd, rndt, 16);
+		}
+		update_xfact();
+	}
+}
+
+void update_rnd_str(const char *ch)
+{
+	while (ch && *ch)
+		update_rnd((unsigned char)*(ch++));
+}
+
+
+void decode_element(gnutls_datum_t *decode, const char *encoded)
+{
+	int s;
+	gnutls_datum_t _base64;
+	size_t base64len, reslen, reslen2;
+
+	base64len = strlen(encoded);
+	reslen2 = reslen = (3 * base64len + 1) / 4;
+
+	_base64.data = (unsigned char*)encoded;
+	_base64.size = base64len;
+
+	decode->size = reslen;
+	decode->data = xmalloc(reslen);
+	s = gnutls_srp_base64_decode(&_base64, (char*)decode->data, &reslen2);
+	if (s < 0)
+		die("Unable to decode base64 data");
+	else if (reslen != reslen2)
+		die("Base64 dlength calculation incorrect. Calculated %lu, "
+			"got %lu", (unsigned long)reslen, (unsigned long)reslen2);
+}
+
+unsigned char *encode_element(gnutls_datum_t *data)
+{
+	int s;
+	size_t reslen2;
+	unsigned char *res;
+
+	reslen2 = (4 * data->size + 2) / 3;
+
+	res = xmalloc(reslen2 + 1);
+	s = gnutls_srp_base64_encode(data, (char*)res, &reslen2);
+	if (s < 0)
+		die("Unable to encode base64 data");
+	res[reslen2] = '\0';
+	return res;
+}
+
+char *generate_srp_line(const char *username,
+	const char *password, const char *junk, struct field *field)
+{
+	gnutls_datum_t salt;
+	gnutls_datum_t g;
+	gnutls_datum_t n;
+	gnutls_datum_t res;
+	int s;
+	char *retline = NULL;
+	unsigned char *encoded_salt;
+	unsigned char *encoded_verifier;
+	update_rnd_str(username);
+	update_rnd_str(":::::");
+	update_rnd_str(junk);
+
+	salt.data = rnd;
+	salt.size = 16;
+	decode_element(&g, field->f_generator);
+	decode_element(&n, field->f_prime);
+
+	s = gnutls_srp_verifier(username, password, &salt, &g, &n, &res);
+	if (s < 0)
+		die("Unable to generate SRP verifier: %s",
+			gnutls_strerror(s));
+
+	encoded_verifier = encode_element(&res);
+	encoded_salt = encode_element(&salt);
+
+	retline = xmalloc(5 + strlen(username) +
+		strlen((char*)encoded_salt) +
+		strlen((char*)encoded_verifier) +
+		strlen(field->f_generator) +
+		strlen(field->f_prime));
+	retline[0] = '\0';
+	strcat(retline, username);
+	strcat(retline, ":");
+	strcat(retline, (char*)encoded_salt);
+	strcat(retline, ":");
+	strcat(retline, (char*)encoded_verifier);
+	strcat(retline, ":");
+	strcat(retline, field->f_generator);
+	strcat(retline, ":");
+	strcat(retline, field->f_prime);
+
+	free(encoded_verifier);
+	free(encoded_salt);
+	free(res.data);
+	free(g.data);
+	free(n.data);
+
+	return retline;
+}
+
+#define LINELEN 69
+
+static void flush_to_file(FILE *out, const char *srpline)
+{
+	char linebuffer[LINELEN + 2];
+
+	linebuffer[LINELEN] = '\\';
+	linebuffer[LINELEN + 1] = '\0';
+
+	while (*srpline) {
+		size_t r;
+		strncpy(linebuffer, srpline, LINELEN);
+		r = strlen(srpline);
+		if (r == LINELEN)
+			linebuffer[LINELEN] = '\0';
+		if (r <= LINELEN)
+			srpline += r;
+		else
+			srpline += LINELEN;
+		fprintf(out, "%s\n", linebuffer);
+	}
+}
+
+int fill_rnd()
+{
+	int fd;
+	int fill = 0;
+
+	fd = open("/dev/urandom", O_RDONLY);
+	if (fd < 0)
+		return 0;
+
+	while (fill < 16) {
+		ssize_t r;
+		r = read(fd, rnd + fill, 16 - fill);
+		if (r < 0 && errno != EINTR && errno != EAGAIN) {
+			close(fd);
+			return 0;
+		} else if (r == 0) {
+			close(fd);
+			return 0;
+		} else {
+			fill += r;
+		}
+	}
+	close(fd);
+	return 1;
+}
+
+int main(int argc, char **argv)
+{
+	int idx, midx = 0;
+	char *username = NULL;
+	char *password = NULL;
+	char *password2 = NULL;
+	char *junk = NULL;
+	char *field = NULL;
+	char *file = NULL;
+	char *ans = NULL;
+	char *end = NULL;
+	FILE *filp = stdout;
+
+	if (argc > 1 && !strcmp(argv[1], "--help"))
+		do_help();
+
+username_again:
+	free(username);
+	username = prompt_string("Enter username", 0);
+	if (!*username) {
+		fprintf(stderr, "Error: Bad username\n");
+		goto username_again;
+	}
+
+	if (fill_rnd())
+		goto no_junk_prompt;
+junk_again:
+	free(junk);
+	junk = prompt_string("Enter some garbage from keyboard (min 32 "
+		"chars)", 1);
+	if (strlen(junk) < 32) {
+		fprintf(stderr, "Error: Garbage needs to be at least "
+			"32 characters\n");
+		goto junk_again;
+	}
+no_junk_prompt:
+
+passwords_again:
+	free(password);
+	free(password2);
+	password = prompt_string("Enter password", 1);
+	password2 = prompt_string("Enter password again", 1);
+	if (strcmp(password, password2)) {
+		fprintf(stderr, "Error: Passwords don't match\n");
+		goto passwords_again;
+	}
+
+field_again:
+	free(field);
+	for (midx = 0; fields[midx].f_name; midx++) {
+		printf("%i) %s\n", midx + 1, fields[midx].f_name);
+	}
+	field = prompt_string("Pick field", 0);
+	idx = (int)strtoul(field, &end, 10) - 1;
+	if (idx < 0 || idx >= midx || !*field || *end) {
+		printf("%i %i %i %i\n", idx, midx, *field, *end);
+		fprintf(stderr, "Error: Invalid choice\n");
+		goto field_again;
+	}
+
+file_again:
+	file = prompt_string("Filename to save as (enter for dump to "
+		"terminal)", 0);
+	if (*file) {
+		filp = fopen(file, "w");
+		if (!filp) {
+			fprintf(stderr, "Can't open \"%s\"\n", file);
+			goto file_again;
+		}
+	}
+
+	ans = generate_srp_line(username, password, junk, fields + idx);
+	flush_to_file(filp, ans);
+	if (filp != stdout)
+		fclose(filp);
+	return 0;
+}
diff --git a/git-over-tls/getkeyid.c b/git-over-tls/getkeyid.c
new file mode 100644
index 0000000..091e60b
--- /dev/null
+++ b/git-over-tls/getkeyid.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "certificate.h"
+#include "home.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void do_help()
+{
+	printf("gits-get-key-name: Get name for keypair or hostkey.\n");
+	printf("Command line options:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("<keyfile>\n");
+	printf("\tRead key file <keyfile>. Name must contain at least\n");
+	printf("\tone '/'\n");
+	printf("<keyname>\n");
+	printf("\tRead key named <keyname>. Name must not contain\n");
+	printf("\t'/'\n");
+	printf("\n");
+	printf("Note: These key names are used in hostkey database and\n");
+	printf("as user names seen by authorization program.\n");
+	exit(0);
+}
+
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+
+int main(int argc, char **argv)
+{
+	struct certificate certificate;
+	gnutls_openpgp_crt_t cert;
+	char filename[PATH_MAX + 1];
+	unsigned char key[KEYBUF];
+	int s;
+	unsigned vout;
+
+	if (argc != 2) {
+		fprintf(stderr, "syntax: %s <keyname>\n", argv[0]);
+		fprintf(stderr, "syntax: %s <keyfile>\n", argv[0]);
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "--help"))
+		do_help();
+
+	if (strchr(argv[1], '/'))
+		s = snprintf(filename, PATH_MAX + 1, "%s", argv[1]);
+	else
+		s = snprintf(filename, PATH_MAX + 1, "%s/.gits/keys/%s",
+			get_home(), argv[1]);
+	if (s < 0 || s > PATH_MAX)
+		die("Insanely long homedir/keyname");
+
+	s = gnutls_global_init();
+	if (s < 0)
+		die("Can't initialize GnuTLS: %s",
+			gnutls_strerror(s));
+
+
+	certificate = parse_certificate(filename, &s);
+	if (s) {
+		if (s == CERTERR_NOCERT)
+			die("Can't find key %s", filename);
+		else if (s == CERTERR_CANTREAD)
+			die_errno("Can't read key");
+		else
+			die("Can't parse key: %s",
+				cert_parse_strerr(s));
+	}
+
+	s = gnutls_openpgp_crt_init(&cert);
+	if (s < 0)
+		die("Can't allocate space for key: %s",
+			gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_import(cert, &certificate.public_key,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+	if (vout)
+		die("Bad key: Validation failed");
+
+	vout = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+	if (s < 0)
+		die("Bad key: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	printf("openpgp-");
+	for (s = 0; s < (int)vout; s++)
+		printf("%02x", key[s]);
+	printf("\n");
+	return 0;
+}
diff --git a/git-over-tls/gits-send-special-command b/git-over-tls/gits-send-special-command
new file mode 100755
index 0000000..ade530d
--- /dev/null
+++ b/git-over-tls/gits-send-special-command
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (C) Ilari Liusvaara 2009
+#
+# This code is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+
+if test "x${1}" == "x--help"
+then
+	echo "gits-send-special-command: Send special command to "
+	echo "server"
+	echo "command line:"
+	echo "--help"
+	echo -e "\x09This help"
+	echo "<service> <URL>"
+	echo -e "\x09Send request for <service> to specified <URL>."
+	exit 0
+fi
+
+git-remote-gits --service=$1 $2
diff --git a/git-over-tls/home.c b/git-over-tls/home.c
new file mode 100644
index 0000000..b41dbfa
--- /dev/null
+++ b/git-over-tls/home.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+const char *get_home()
+{
+	static char *home = NULL;
+	const char *tmpans;
+#ifndef WIN32
+	struct passwd *user;
+#endif
+
+	if (home)
+		return home;
+
+	if (getenv("HOME")) {
+		tmpans = getenv("HOME");
+		goto got_it;
+	}
+
+#ifndef WIN32
+	user = getpwuid(getuid());
+	if (user && user->pw_dir) {
+		tmpans = user->pw_dir;
+		goto got_it;
+	}
+#endif
+
+	die("Can't obtain home directory of current user");
+got_it:
+	home = xstrdup(tmpans);
+	return home;
+}
diff --git a/git-over-tls/home.h b/git-over-tls/home.h
new file mode 100644
index 0000000..133ee78
--- /dev/null
+++ b/git-over-tls/home.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _home__h__included__
+#define _home__h__included__
+
+const char *get_home();
+
+#endif
diff --git a/git-over-tls/hostkey.c b/git-over-tls/hostkey.c
new file mode 100644
index 0000000..28df0e5
--- /dev/null
+++ b/git-over-tls/hostkey.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009-2010
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "hostkey.h"
+#include "home.h"
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <gnutls/openpgp.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+/* Be ready in case some joker decides to use 1024 bit hash as fingerprint. */
+#define KEYBUF 128
+#define MAXLINE 2048
+
+void check_hostkey(gnutls_session_t session, const char *hostname)
+{
+	const gnutls_datum_t *certificate = NULL;
+	unsigned int cert_size = 0;
+	gnutls_openpgp_crt_t cert;
+	int s;
+	unsigned int vout;
+	unsigned char key[KEYBUF];
+	FILE *hostfile;
+	char hostfilepath[PATH_MAX + 1];
+	char linebuffer[MAXLINE];
+
+	certificate = gnutls_certificate_get_peers(session, &cert_size);
+	if (!certificate)
+		die("Server didn't send a hostkey");
+
+	s = gnutls_openpgp_crt_init(&cert);
+	if (s < 0)
+		die("Can't allocate space for hostkey: %s",
+			gnutls_strerror(s));
+
+	s = gnutls_openpgp_crt_import(cert, certificate,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+	/* Defend against subkey attack. */
+	s = gnutls_openpgp_crt_get_subkey_count(cert);
+	if (s != 0)
+		die("Server sent bad hostkey: Subkeys are not allowed");
+
+	s = gnutls_openpgp_crt_verify_self(cert, 0, &vout);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+	if (vout)
+		die("Server sent bad hostkey: Validation failed");
+
+	vout = KEYBUF;
+	s = gnutls_openpgp_crt_get_fingerprint(cert, key, &vout);
+	if (s < 0)
+		die("Server sent bad hostkey: %s", gnutls_strerror(s));
+
+	gnutls_openpgp_crt_deinit(cert);
+
+	s = snprintf(hostfilepath, PATH_MAX + 1, "%s/.gits/hostkeys",
+		get_home());
+	if (s < 0 || s > PATH_MAX)
+		die("Home directory path insanely long");
+
+	hostfile = fopen(hostfilepath, "r");
+	if (!hostfile)
+		die_errno("Can't open .gits/hostkeys");
+
+	while (fgets(linebuffer, MAXLINE - 2, hostfile)) {
+		char *split;
+		if (!*linebuffer || *linebuffer == '#')
+			continue;
+		if (linebuffer[strlen(linebuffer) - 1] == '\n')
+			linebuffer[strlen(linebuffer) - 1] = '\0';
+
+		split = strchr(linebuffer, ' ');
+		if (!split)
+			continue;
+		*split = '\0';
+		if (strcmp(linebuffer, hostname))
+			continue;
+
+		/*
+		 * Be nice to users and strip this in case it gets
+		 * retained from key id calculator.
+		 */
+		if (!strncmp(split + 1, "openpgp-", 8))
+			split += 8;
+
+		for (s = 0; s < vout; s++) {
+			char buffer[3];
+			sprintf(buffer, "%02x", (int)key[s]);
+			if (buffer[0] != split[2 * s + 1])
+				die("HOST KEY MISMATCH FOR HOST %s!",
+					hostname);
+			if (buffer[1] != split[2 * s + 2])
+				die("HOST KEY MISMATCH FOR HOST %s!",
+					hostname);
+		}
+		if (split[2 * vout + 1])
+			die("HOST KEY MISMATCH FOR HOST %s!",
+				hostname);
+		goto ok;
+	}
+	die("Hostkey for %s not found in hostkeys list", hostname);
+ok:
+	fclose(hostfile);
+}
diff --git a/git-over-tls/hostkey.h b/git-over-tls/hostkey.h
new file mode 100644
index 0000000..c0e7dfe
--- /dev/null
+++ b/git-over-tls/hostkey.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _hostkey__h__included__
+#define _hostkey__h__included__
+
+#include <gnutls/gnutls.h>
+
+void check_hostkey(gnutls_session_t session, const char *hostname);
+
+#endif
diff --git a/git-over-tls/hostkeymanager.c b/git-over-tls/hostkeymanager.c
new file mode 100644
index 0000000..2df09e0
--- /dev/null
+++ b/git-over-tls/hostkeymanager.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "home.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+#define BUFSIZE 8192
+
+static void do_help()
+{
+	printf("gits-hostkey: Add, update or delete entries in hostkey.\n");
+	printf("database.\n");
+	printf("Command line:\n");
+	printf("--help\n");
+	printf("\tThis help\n");
+	printf("list\n");
+	printf("\tList hosts known and their keys\n");
+	printf("add <host> <key>\n");
+	printf("\tAdd <host> to database with key <key>\n");
+	printf("update <host> <key>\n");
+	printf("\tUpdate <host> to database to use key <key>\n");
+	printf("delete <host>\n");
+	printf("\tDelete key for <host>\n");
+	exit(0);
+}
+
+
+struct hostkey
+{
+	/* Hostname. NULL if not valid line. */
+	char *h_hostname;
+	/* Hostkey. NULL if not valid line. */
+	char *h_hostkey;
+	/* Line. Non-NULL if hostname/hoskey is NULL. */
+	char *h_line;
+	/* Next line. */
+	struct hostkey *h_next;
+};
+
+struct hostkey *first_hostkey = NULL;
+struct hostkey *last_hostkey = NULL;
+
+int ensure_leading_directories()
+{
+	struct stat s;
+	char fsobj[BUFSIZE];
+	int r;
+
+	sprintf(fsobj, "%s/.gits", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits");
+	else if (r == 0 && !S_ISDIR(s.st_mode))
+		die("$HOME/.gits exists but is not a directory");
+	else if (r < 0) {
+		/* Need to create it. */
+		if (mkdir(fsobj, 0700) < 0)
+			die_errno("Create $HOME/.gits failed");
+	}
+	/* Otherwise, r == 0 && S_ISDIR(s), which is OK. */
+	sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+	r = stat(fsobj, &s);
+	if (r < 0 && errno != ENOENT)
+		die_errno("Stat $HOME/.gits/hostkeys");
+	else if (r == 0 && !S_ISREG(s.st_mode))
+		die("$HOME/.gits/hostkeys exists but is not a file");
+	/* Doesn't exist or a regular file. OK. */
+	return (r == 0) ? 1 : 0;
+}
+
+void load_hostkeys()
+{
+	char fsobj[BUFSIZE];
+	char linebuf[BUFSIZE];
+	FILE *filp;
+
+	if (!ensure_leading_directories())
+		return;
+
+	sprintf(fsobj, "%s/.gits/hostkeys", get_home());
+	filp = fopen(fsobj, "r");
+	if (filp == NULL)
+		die("Can't open $HOME/.gits/hostkeys");
+
+	while (fgets(linebuf, BUFSIZE - 2, filp)) {
+		struct hostkey *h;
+		char *split;
+		h = xmalloc(sizeof(struct hostkey));
+		h->h_next = NULL;
+		h->h_line = xstrdup(linebuf);
+
+		if (*h->h_line && h->h_line[strlen(h->h_line) - 1] == '\n')
+			h->h_line[strlen(h->h_line) - 1] = '\0';
+
+		if (!*h->h_line || h->h_line[0] == '#')
+			goto not_valid;
+		split = strchr(h->h_line, ' ');
+		if (!split)
+			goto not_valid;
+
+		*split = '\0';
+		h->h_hostname = h->h_line;
+		h->h_hostkey = xstrdup(split + 1);
+		h->h_line = NULL;
+not_valid:
+		if (last_hostkey)
+			last_hostkey = last_hostkey->h_next = h;
+		else
+			first_hostkey = last_hostkey = h;
+	}
+	if (!feof(filp))
+		die("Error reading $HOME/.gits/hostkeys");
+	fclose(filp);
+}
+
+void save_hostkeys()
+{
+	char fsobj[BUFSIZE];
+	char fsobj2[BUFSIZE];
+	FILE *filp;
+	struct hostkey *host;
+
+	sprintf(fsobj, "%s/.gits/hostkeys.tmp", get_home());
+	sprintf(fsobj2, "%s/.gits/hostkeys", get_home());
+	filp = fopen(fsobj, "w");
+	if (filp == NULL)
+		die("Can't open $HOME/.gits/hostkeys.tmp");
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		if (host->h_line) {
+			if (fprintf(filp, "%s\n", host->h_line) < 0)
+				die("hostkeys write error");
+		} else {
+			if (fprintf(filp, "%s %s\n", host->h_hostname,
+				host->h_hostkey) < 0)
+				die("hostkeys write error");
+		}
+	}
+
+	if (fclose(filp) < 0)
+		die("Hostkeys write error");
+
+	if (rename(fsobj, fsobj2) < 0)
+		die("Error renaming hostkeys");
+}
+
+void list_hostkeys()
+{
+	struct hostkey *host;
+	size_t longest_hostname = 0;
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		size_t hostnamelen = 0;
+		hostnamelen = host->h_hostname ? strlen(host->h_hostname) : 0;
+		if (longest_hostname < hostnamelen)
+			longest_hostname = hostnamelen;
+	}
+
+	for (host = first_hostkey; host; host = host->h_next) {
+		size_t pad;
+		size_t i;
+		if (!host->h_hostname)
+			continue;
+		pad = longest_hostname + 1 - strlen(host->h_hostname);
+		printf("%s", host->h_hostname);
+		for (i = 0; i < pad; i++)
+			printf(" ");
+		printf("%s\n", host->h_hostkey);
+	}
+}
+
+void add_hostkey(const char *host, const char *key)
+{
+	struct hostkey *h;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host))
+			die("Host %s already in hostkeys", host);
+	}
+
+	h = xmalloc(sizeof(struct hostkey));
+	h->h_next = NULL;
+	h->h_line = NULL;
+	h->h_hostname = xstrdup(host);
+	h->h_hostkey = xstrdup(key);
+
+	if (last_hostkey)
+		last_hostkey = last_hostkey->h_next = h;
+	else
+		first_hostkey = last_hostkey = h;
+}
+
+void update_hostkey(const char *host, const char *key)
+{
+	struct hostkey *h;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host)) {
+			h->h_hostkey = xstrdup(key);
+			return;
+		}
+	}
+	die("Host %s not found in hostkeys", host);
+}
+
+void delete_hostkey(const char *host)
+{
+	struct hostkey *h;
+	struct hostkey *prev = NULL;
+
+	for (h = first_hostkey; h; h = h->h_next) {
+		if (!h->h_hostname)
+			continue;
+		if (!strcmp(h->h_hostname, host)) {
+			if (prev)
+				prev->h_next = h->h_next;
+			else
+				first_hostkey = h->h_next;
+			free(h);
+			return;
+		}
+		prev = h;
+	}
+	die("Host %s not found in hostkeys", host);
+}
+
+int main(int argc, char **argv)
+{
+	if (argc < 2) {
+		fprintf(stderr, "syntax: %s list\n", argv[0]);
+		fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+		return 1;
+	}
+	if (!strcmp(argv[1], "--help"))
+		do_help();
+
+	if (!strcmp(argv[1], "list")) {
+		if (argc != 2) {
+			fprintf(stderr, "syntax: %s list\n", argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		list_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "add")) {
+		if (argc != 4) {
+			fprintf(stderr, "syntax: %s add <host> <key>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		add_hostkey(argv[2], argv[3]);
+		save_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "update")) {
+		if (argc != 4) {
+			fprintf(stderr, "syntax: %s update <host> <key>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		update_hostkey(argv[2], argv[3]);
+		save_hostkeys();
+		return 0;
+	} else if (!strcmp(argv[1], "delete")) {
+		if (argc != 3) {
+			fprintf(stderr, "syntax: %s delete <host>\n",
+				argv[0]);
+			return 1;
+		}
+
+		load_hostkeys();
+		delete_hostkey(argv[2]);
+		save_hostkeys();
+		return 0;
+	} else {
+		fprintf(stderr, "syntax: %s list\n", argv[0]);
+		fprintf(stderr, "syntax: %s add <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s update <host> <key>\n", argv[0]);
+		fprintf(stderr, "syntax: %s delete <host>\n", argv[0]);
+		return 1;
+	}
+}
diff --git a/git-over-tls/keypairs.c b/git-over-tls/keypairs.c
new file mode 100644
index 0000000..cc77217
--- /dev/null
+++ b/git-over-tls/keypairs.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "keypairs.h"
+#include "home.h"
+#include "certificate.h"
+#include <stdio.h>
+#include <limits.h>
+#include <gnutls/openpgp.h>
+#include <errno.h>
+#ifdef USE_COMPAT_H
+#include "compat.h"
+#else
+#include "git-compat-util.h"
+#endif
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+	const char *username)
+{
+	char keypath[PATH_MAX + 1];
+	const char *home;
+	struct certificate cert;
+	int r;
+
+	home = get_home();
+
+	r = snprintf(keypath, PATH_MAX + 1, "%s/.gits/keys/%s", home,
+		username);
+	if (r < 0 || r > PATH_MAX) {
+		die("Username too long");
+	}
+
+	cert = parse_certificate(keypath, &r);
+	if (r) {
+		if (r == CERTERR_NOCERT)
+			return -1;
+		if (r == CERTERR_CANTREAD)
+			die_errno("Can't read keypair");
+		else
+			die("Can't read keypair: %s",
+				cert_parse_strerr(r));
+	}
+
+	r = gnutls_certificate_set_openpgp_keyring_mem(creds,
+		cert.public_key.data, cert.public_key.size,
+		GNUTLS_OPENPGP_FMT_RAW);
+	if (r < 0)
+		die("Can't load public key: %s", gnutls_strerror(r));
+
+	r = gnutls_certificate_set_openpgp_key_mem(creds, &cert.public_key,
+		&cert.private_key, GNUTLS_OPENPGP_FMT_RAW);
+	if (r < 0)
+		die("Can't load keypair: %s", gnutls_strerror(r));
+
+	return 0;
+}
diff --git a/git-over-tls/keypairs.h b/git-over-tls/keypairs.h
new file mode 100644
index 0000000..11f4ef7
--- /dev/null
+++ b/git-over-tls/keypairs.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) Ilari Liusvaara 2009
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _keypairs__h__included__
+#define _keypairs__h__included__
+
+#include <gnutls/openpgp.h>
+
+int select_keypair_int(gnutls_certificate_credentials_t creds,
+	const char *username);
+
+#endif
-- 
1.6.6.102.gd6f8f.dirty

^ permalink raw reply related

* [RFC 0/2] Git-over-TLS (gits://) client side support
From: Ilari Liusvaara @ 2010-01-13 13:19 UTC (permalink / raw)
  To: git

This is client-side support for Git-over-TLS (gits://). gits:// is
version of git:// protocol layered on top of TLS (Transport Layer
Security). If using TLS, it is autenticated transport supporing
fetching, pushing and remote archive (plus special commands that
have server-dependent meaning).

Needs GnuTLS, and adds new make option NO_GNUTLS that disables builing
this code.

Supported underlying stream transports include TCP/IP, TCP/IPv6 and
Unix domain sockets (including Linux abstract namespace).

Supported authentication mechanisms include passwords, keypairs and on
some platforms Unix authentication if using unix domain sockets. Server
is authenticated using keypair (hostkey).

The patch is split into two parts because it would be otherwise be
too large for this list. Included are all the needed client side
utilities (some of them run gpg internally).

The main repo for gits:// implementation is 
git://repo.or.cz/git-daemon2.git , which includes selfstanding client
code and server code.

Ilari Liusvaara (2):
  Git-over-TLS (gits://) client side support (part 1 of 2)
  Git-over-TLS (gits://) client side support (part 2 of 2)

 Makefile                               |   23 +-
 git-over-tls/.gitignore                |    5 +
 git-over-tls/Makefile                  |   46 ++
 git-over-tls/cbuffer.c                 |  504 ++++++++++++
 git-over-tls/cbuffer.h                 |  304 +++++++
 git-over-tls/certificate.c             |  306 +++++++
 git-over-tls/certificate.h             |   28 +
 git-over-tls/connect.c                 |  263 ++++++
 git-over-tls/connect.h                 |   14 +
 git-over-tls/genkeypair.c              |   38 +
 git-over-tls/gensrpverifier.c          |  372 +++++++++
 git-over-tls/getkeyid.c                |  118 +++
 git-over-tls/gits-send-special-command |   22 +
 git-over-tls/home.c                    |   47 ++
 git-over-tls/home.h                    |   13 +
 git-over-tls/hostkey.c                 |  116 +++
 git-over-tls/hostkey.h                 |   15 +
 git-over-tls/hostkeymanager.c          |  305 +++++++
 git-over-tls/keypairs.c                |   60 ++
 git-over-tls/keypairs.h                |   16 +
 git-over-tls/main.c                    |  460 +++++++++++
 git-over-tls/misc.c                    |   15 +
 git-over-tls/misc.h                    |   27 +
 git-over-tls/mkcert.c                  |  507 ++++++++++++
 git-over-tls/prompt.c                  |  100 +++
 git-over-tls/prompt.h                  |   18 +
 git-over-tls/srp_askpass.c             |   90 ++
 git-over-tls/srp_askpass.h             |   14 +
 git-over-tls/user.c                    | 1384 ++++++++++++++++++++++++++++++++
 git-over-tls/user.h                    |  357 ++++++++
 30 files changed, 5585 insertions(+), 2 deletions(-)
 create mode 100644 git-over-tls/.gitignore
 create mode 100644 git-over-tls/Makefile
 create mode 100644 git-over-tls/cbuffer.c
 create mode 100644 git-over-tls/cbuffer.h
 create mode 100644 git-over-tls/certificate.c
 create mode 100644 git-over-tls/certificate.h
 create mode 100644 git-over-tls/connect.c
 create mode 100644 git-over-tls/connect.h
 create mode 100644 git-over-tls/genkeypair.c
 create mode 100644 git-over-tls/gensrpverifier.c
 create mode 100644 git-over-tls/getkeyid.c
 create mode 100755 git-over-tls/gits-send-special-command
 create mode 100644 git-over-tls/home.c
 create mode 100644 git-over-tls/home.h
 create mode 100644 git-over-tls/hostkey.c
 create mode 100644 git-over-tls/hostkey.h
 create mode 100644 git-over-tls/hostkeymanager.c
 create mode 100644 git-over-tls/keypairs.c
 create mode 100644 git-over-tls/keypairs.h
 create mode 100644 git-over-tls/main.c
 create mode 100644 git-over-tls/misc.c
 create mode 100644 git-over-tls/misc.h
 create mode 100644 git-over-tls/mkcert.c
 create mode 100644 git-over-tls/prompt.c
 create mode 100644 git-over-tls/prompt.h
 create mode 100644 git-over-tls/srp_askpass.c
 create mode 100644 git-over-tls/srp_askpass.h
 create mode 100644 git-over-tls/user.c
 create mode 100644 git-over-tls/user.h

^ permalink raw reply

* remote to push to local branch: hung up unexpectedly
From: Michael S. Tsirkin @ 2010-01-13 13:08 UTC (permalink / raw)
  To: git

I have this in .config:

[remote "anthony"]
        url = .
        push = +refs/heads/pci:refs/heads/for_anthony

The point is to save state when it's stable and safe for outside use.

I hoped to make this shortcut for:
git push  . +refs/heads/pci:refs/heads/for_anthony

But this does:

Total 0 (delta 0), reused 0 (delta 0)
To .
 * [new branch]      pci -> for_anthony
fatal: The remote end hung up unexpectedly

So the command works but seems to give an error at the end.
Same effect with url = file://.
with url = /scm/qemu   (this is repo path)
and with url = file:///scm/qemu

reproduced with 1.6.6.144 and with 1.6.2.5

-- 
MST

^ permalink raw reply

* Re: discussion: an option to fail git fetch if a pulled branch tip is not a fast forward of the existing remote tip?
From: Thomas Rast @ 2010-01-13 12:59 UTC (permalink / raw)
  To: Jon Seymour; +Cc: Git Mailing List
In-Reply-To: <2cfc40321001130354w626ec0fat7abdfaff9771c29f@mail.gmail.com>

Jon Seymour wrote:
[discussion of a case of branch rewriting, called "backtracking" here]
> Now clearly the upstream developer should not have backtracked. That
> said, it would have been nice if I could have easily configured my
> porcelain to detect the backtracking condition. An option on git fetch
> that implemented something similar to the git push check would have
> made this easy to achieve.

This is an instance of a non-fast-foward update, which is indicated by
the little "+" in git-fetch's output.  E.g., fetching git.git a moment
ago gave me

>From git://git.kernel.org/pub/scm/git/git
   fbb9971..8efa5f6  maint      -> origin/maint
   c0eb604..054d2fa  master     -> origin/master
   e295b7f..e84eab0  next       -> origin/next
 + 10659b7...6a048fc pu         -> origin/pu  (forced update)

which means that the 'pu' branch was a non-fast-forward update.

If you do not want to rely on checking the results manually, you can
edit the remote configuration.  Normally, it will look like

[remote "origin"]
        url = git://git.kernel.org/pub/scm/git/git.git
        fetch = +refs/heads/*:refs/remotes/origin/*

Note the "+" in the 'fetch' line, which means "allow non-fast-forward
updates".  Removing the + (leaving the rest intact) results in

>From git://git.kernel.org/pub/scm/git/git
 ! [rejected]        pu         -> origin/pu  (non-fast-forward)

You can then manually add lines _with_ the "+" for branches where you
do want to allow non-ff updates, e.g., for git.git I might say

[remote "origin"]
        url = git://git.kernel.org/pub/scm/git/git.git
        fetch = +refs/heads/pu:refs/remotes/origin/pu
        fetch = refs/heads/*:refs/remotes/origin/*

since only 'pu' will be rewritten regularly. ('next' gets the
occasional treatment too, so that config would not be very
futureproof.)

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply

* Re: [PATCH 1/5] MSVC: Windows-native implementation for subset of Pthreads API
From: Dmitry Potapov @ 2010-01-13 12:53 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: kusmabite, msysgit, git, Andrzej K. Haczewski
In-Reply-To: <201001122213.38287.j6t@kdbg.org>

On Tue, Jan 12, 2010 at 10:13:38PM +0100, Johannes Sixt wrote:
> On Freitag, 8. Januar 2010, Erik Faye-Lund wrote:
> > On Fri, Jan 8, 2010 at 4:32 AM, Dmitry Potapov <dpotapov@gmail.com> wrote:
> > > AFAIK, Win32 API assumes that reading LONG is always atomic, so
> > > the critical section is not really necesary here, but you need
> > > to declare 'waiters' as 'volatile':
> >
> > "Simple reads and writes to properly-aligned 32-bit variables are
> > atomic operations."
> > http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx
> 
> But then the next sentence is:
> 
> "However, access is not guaranteed to be synchronized. If two threads are 
> reading and writing from the same variable, you cannot determine if one 
> thread will perform its read operation before the other performs its write 
> operation."
> 
> This goes without saying, IOW, those Microsofties don't know what they write, 
> which makes the documentation a bit less trustworthy.

The fact that Microsoft documentation is not written by brightest people
in the world is well known...

> 
> Nevertheless, I rewrote the code to use Interlocked* functions, and then read 
> the documentation again. InterlockedIncrement reads, for example:
> 
> "... This function is atomic with respect to calls to other interlocked 
> functions."

I have no clue what the author meant here. Perhaps Microsoft wanted to
reserve the right to implement Interlocked functions using an internal
lock on those architectures that do not have atomic operations. (For
instance, ARMv5 does not have atomic operations).

But any sane implementation of a critical section primitive requires
some operation that is atomic with respect to the user space (or you
kill the performance by calling some syscall in noncontentious case).
For instance, the Linux kernel provides this possibility by providing
__kernel_cmpxchg for ARM, which can be used to implement all other
synchronization primitives such mutexes and conditions. (Or on some
small MMU-less embedded system, disabling interrupts or the scheduler
lock is used). So, any sane implementation should atomic not only in
respect to other Interlock functions but also other synchronization
primitives.

In any case, on x86, it is implemented as _InterlockedIncrement, which
is a built-in function that generates the appropriate assembler instruction.

> 
> In particular, it doesn't say that it is atomic WRT reads such as we have 
> here:
> 
> > >> +     /* we're done waiting, so make sure we decrease waiters count */
> > >> +     EnterCriticalSection(&cond->waiters_lock);
> > >> +     --cond->waiters;
> > >> +     LeaveCriticalSection(&cond->waiters_lock);

and these lines should be replaced with

  InterlockedDecrement(&cond->waiters)

so it will be safe even on utterly idiotic implementation of Interlocked
functions that uses some internal lock; and as I said earlier on x86,
Interlocked functions are translated in appropriate assembler instructions.


Dmitry

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox