From 4a1a73e8fc0fc6d2c17dca416222e487b34d019a Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sat, 11 Jan 2014 18:06:34 +0000 Subject: [PATCH] dgit-repos-push-receiver: wip --- dgit-repos-push-receiver | 229 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 dgit-repos-push-receiver diff --git a/dgit-repos-push-receiver b/dgit-repos-push-receiver new file mode 100644 index 00000000..486cd167 --- /dev/null +++ b/dgit-repos-push-receiver @@ -0,0 +1,229 @@ +#!/usr/bin/perl -w +# dgit-repos-push-receiver +# +# usages: +# .../dgit-repos-push-receiver DGIT-REPOS-DIR --ssh +# .../dgit-repos-push-receiver DGIT-REPOS-DIR PACKAGE +# internal usage: +# .../dgit-repos-push-receiver --pre-receive-hook PACKAGE +# +# Invoked as the ssh restricted command +# +# Works like git-receive-pack + +use strict; + +# What we do is this: +# - extract the destination repo name somehow +# - make a hardlink clone of the destination repo +# - provide the destination with a stunt pre-receive hook +# - run actual git-receive-pack with that new destination +# as a result of this the stunt pre-receive hook runs; it does this +# find the keyring(s) to use for verification +# verify the signed tag +# check that the signed tag has a suitable name +# parse the signed tag body to extract the intended +# distro and suite +# check that the distro is right +# check that the suite is the same as the branch we are +# supposed to update +# check that the signed tag refers to the same commit +# as the new suite +# check that the signer was correct +# push the signed tag to the actual repo +# push the new dgit branch head to the actual repo + +use POSIX; +use Fcntl qw(:flock); + +our $package_re = '[0-9a-z][-+.0-9a-z]+'; + +our $dgitrepos; +our $package; +our $destrepo; +our $workrepo; + +sub acquirelock ($$) { + my ($lock, $must) = @_; + for (;;) { + my $fh = new IO::File, ">", $lock or die "open $lock: $!"; + my $ok = flock $fh, $must ? LOCK_EX : (LOCK_EX|LOCK_NB); + if (!$ok) { + return unless $must; + die "flock $lock: $!"; + } + if (!stat $lock) { + next if $! == ENOENT; + die "stat $lock: $!"; + } + my $want = (stat _)[1]; + stat $fh or die $!; + my $got = (stat _)[1]; + return $fh if $got == $want; + } +} + +sub makeworkingclone () { + $workrepo = "$dgitrepos/_tmp/$package_incoming$$"; + my $lock = "$workrepo.lock"; + my $lockfh = acquirelock($lock, 1); + if (!stat $destrepo) { + $! == ENOENT or die "stat dest repo $destrepo: $!"; + mkdir $workrepo or die "create work repo $workrepo: $!"; + runcmd qw(git init --bare), $workrepo; + } else { + runcmd qw(git clone -l -q --mirror), $destrepo, $workrepo; + } +} + +sub setupstunthook () { + my $prerecv = "$workrepo/hooks/pre-receive"; + my $fh = new IO::File, $prerecv, O_WRONLY|O_CREAT|O_TRUNC, 0777 + or die "$prerecv: $!"; + print $fh <) { + m/^\S+ (\S+) (\S+)$/ or die "$_ ?"; + my ($sha1, $refname) = ($1, $2); + if ($refname =~ m{^refs/tags/(?=debian/)}) { + die if defined $tagname; + $tagname = $'; #'; + $tagval = $sha1; + } elsif ($refname =~ m{^refs/dgit/}) { + die if defined $suite; + $suite = $'; #'; + $commit = $sha1; + } else { + die; + } + } + STDIN->error and die $!; + + die unless defined $refname; + die unless defined $branchname; +} + +sub parsetag () { + open PT, ">dgit-tmp/plaintext" or die $!; + open DS, ">dgit-tmp/plaintext.asc" or die $!; + open T, "-|", qw(git cat-file tag), $tagval or die $!; + my %tagh; + for (;;) { + $!=0; $_=; defined or die $!; + print PT or die $!; + if (m/^(\S+) (.*)/) { + push @{ $tagh{$1} }, $2; + } elsif (!m/\S/) { + last; + } else { + die; + } + } + $!=0; $_=; defined or die $!; + m/^($package_re) release (\S+) for (\S+) \[dgit\]$/ or die; + + die unless $1 eq $package; + $version = $2; + die unless $3 eq $suite; + + for (;;) { + print PT or die $!; + $!=0; $_=; defined or die $!; + last if m/^-----BEGIN PGP/; + } + for (;;) { + print DS or die $!; + $!=0; $_=; + last if !defined; + } + T->error and die $!; + close PT or die $!; + close DS or die $!; +} + +sub checktag () { + tagh1('object') eq $branchval or die; + tagh1('type') eq 'commit' or die; + tagh1('tag') eq $tagname or die; + + my $v = $version; + $v =~ y/~:/_%/; + $tagname eq "debian/$v" or die; +} + + +sub stunthook () { + chdir $workrepo or die "chdir $workrepo: $!"; + mkdir "dgit-tmp" or $!==EEXIST or die $!; + readupdates(); + parsetag(); + verifytag(); + checktag(); +... ... +} + +#----- arg parsing and main program ----- + +sub parseargs () { + die unless @ARGV; + + if ($ARGV[0] eq '--pre-receive-hook') { + shift @ARGV; + @ARGV == 1 or die; + $package = shift @ARGV; + defined($workrepo = $ENV{'DGIT_RPR_WORK'}) or die; + defined($destrepo = $ENV{'DGIT_RPR_DEST'}) or die; + open STDOUT, ">&STDERR" or die $!; + stunthook(); + exit 0; + } + + die if $ARGV[0] =~ m/^-/; + $dgitrepos = shift; + + die unless @ARGV; + if ($ARGV[0] != m/^-/) { + @ARGV == 1 or die; + $package = shift @ARGV; + } elsif ($ARGV[0] eq '--ssh') { + shift @ARGV; + !@ARGV or die; + my $cmd = $ENV{'SSH_ORIGINAL_COMMAND'}; + $cmd =~ m{ + ^ + (?:\S*/)? + (?:dgit-repos-push-receiver|git-receive-pack) + \s+ + (?:\S*/)? + ($package_re)\.git + $ + }ox + or die "requested command $cmd not understood"; + $package = $1; + } else { + die; + } + + $destrepo = "$dgitrepos/$package.git"; +} + +sub main () { + parseargs(); + makeworkingclone(); + setupstunthook(); + runcmd qw(git receive-pack), $destdir; +} -- 2.30.2