2 # dgit-repos-push-receiver
5 # .../dgit-repos-push-receiver KEYRING-AUTH-SPEC DGIT-REPOS-DIR --ssh
6 # .../dgit-repos-push-receiver KEYRING-AUTH-SPEC DGIT-REPOS-DIR PACKAGE
8 # .../dgit-repos-push-receiver --pre-receive-hook PACKAGE
10 # Invoked as the ssh restricted command
12 # Works like git-receive-pack
14 # KEYRING-AUTH-SPEC is a :-separated list of
15 # KEYRING.GPG,AUTH-SPEC
16 # where AUTH-SPEC is one of
23 # - extract the destination repo name somehow
24 # - make a hardlink clone of the destination repo
25 # - provide the destination with a stunt pre-receive hook
26 # - run actual git-receive-pack with that new destination
27 # as a result of this the stunt pre-receive hook runs; it does this
28 # find the keyring(s) to use for verification
29 # verify the signed tag
30 # check that the signed tag has a suitable name
31 # parse the signed tag body to extract the intended
33 # check that the distro is right
34 # check that the suite is the same as the branch we are
36 # check that the signed tag refers to the same commit
38 # check that the signer was correct
39 # push the signed tag to the actual repo
40 # push the new dgit branch head to the actual repo
45 our $package_re = '[0-9a-z][-+.0-9a-z]+';
53 sub acquirelock ($$) {
54 my ($lock, $must) = @_;
56 my $fh = new IO::File, ">", $lock or die "open $lock: $!";
57 my $ok = flock $fh, $must ? LOCK_EX : (LOCK_EX|LOCK_NB);
60 die "flock $lock: $!";
66 my $want = (stat _)[1];
68 my $got = (stat _)[1];
69 return $fh if $got == $want;
73 sub makeworkingclone () {
74 $workrepo = "$dgitrepos/_tmp/${pkg}_incoming$$";
75 my $lock = "$workrepo.lock";
76 my $lockfh = acquirelock($lock, 1);
77 if (!stat $destrepo) {
78 $! == ENOENT or die "stat dest repo $destrepo: $!";
79 mkdir $workrepo or die "create work repo $workrepo: $!";
80 runcmd qw(git init --bare), $workrepo;
82 runcmd qw(git clone -l -q --mirror), $destrepo, $workrepo;
86 sub setupstunthook () {
87 my $prerecv = "$workrepo/hooks/pre-receive";
88 my $fh = new IO::File, $prerecv, O_WRONLY|O_CREAT|O_TRUNC, 0777
89 or die "$prerecv: $!";
90 print $fh <<END or die "$prerecv: $!";
93 exec $0 --pre-receive-hook $pkg
95 close $fh or die "$prerecv: $!";
96 $ENV{'DGIT_RPR_WORK'}= $workrepo;
97 $ENV{'DGIT_RPR_DEST'}= $destrepo;
100 #----- stunt post-receive hook -----
102 our ($tagname, $tagval, $suite, $oldcommit, $commit);
103 our ($version, %tagh);
107 m/^(\S+) (\S+) (\S+)$/ or die "$_ ?";
108 my ($old, $sha1, $refname) = ($1, $2, $3);
109 if ($refname =~ m{^refs/tags/(?=debian/)}) {
110 die if defined $tagname;
113 reject "tag $tagname already exists -".
114 " not replacing previously-pushed version"
116 } elsif ($refname =~ m{^refs/dgit/}) {
117 die if defined $suite;
125 STDIN->error and die $!;
127 die unless defined $refname;
128 die unless defined $branchname;
132 open PT, ">dgit-tmp/plaintext" or die $!;
133 open DS, ">dgit-tmp/plaintext.asc" or die $!;
134 open T, "-|", qw(git cat-file tag), $tagval or die $!;
137 $!=0; $_=<T>; defined or die $!;
139 if (m/^(\S+) (.*)/) {
140 push @{ $tagh{$1} }, $2;
147 $!=0; $_=<T>; defined or die $!;
148 m/^($package_re) release (\S+) for (\S+) \[dgit\]$/ or die;
150 die unless $1 eq $pkg;
152 die unless $3 eq $suite;
156 $!=0; $_=<T>; defined or die $!;
157 last if m/^-----BEGIN PGP/;
169 sub checksig_keyring ($) {
170 my ($keyringfile) = @_;
171 # returns primary-keyid if signed by a key in this keyring
173 # or dies on other errors
177 open P, "-|", (qw(gpgv --status-fd=1),
178 map { '--keyring', $_ }, @keyrings,
179 qw(dgit-tmp/plaintext.asc dgit-tmp/plaintext))
183 next unless s/^\[GNUPG:\]: //;
185 my @l = split / /, $_;
186 if ($l[0] eq 'NO_PUBKEY') {
188 } elsif ($l[0] eq 'VALIDSIG') {
190 $sigtype eq '00' or reject "signature is not of type 00!";
192 die unless defined $ok;
201 sub dm_txt_check ($$) {
202 my ($keyid, $dmtxtfn) = @_;
203 open DT, '<', $dmtxtfn or die "$dmtxtfn $!";
205 m/^fingerprint:\s+$keyid$/oi
208 or reject "key $keyid missing Allow section in permissions!";
213 or reject "package $package not allowed for key $keyid";
217 foreach my $p (split /\s+/) {
218 return if $p eq $package; # yay!
221 DT->error and die $!;
223 reject "key $keyid not in permissions list although in keyring!";
227 foreach my $kas (split /:/, $keyrings) {
228 $kas =~ s/^([^,]+),// or die;
229 my $keyid = checksig_keyring $1;
230 if (defined $keyid) {
231 if ($kas =~ m/^a$/) {
233 } elsif ($kas =~ m/^m([^,]+)$/) {
234 dm_txt_check($keyid, $1);
241 reject "key not found in keyrings";
245 tagh1('object') eq $branchval or die;
246 tagh1('type') eq 'commit' or die;
247 tagh1('tag') eq $tagname or die;
251 $tagname eq "debian/$v" or die;
258 chdir $workrepo or die "chdir $workrepo: $!";
259 mkdir "dgit-tmp" or $!==EEXIST or die $!;
267 #----- arg parsing and main program -----
272 if ($ARGV[0] eq '--pre-receive-hook') {
276 defined($workrepo = $ENV{'DGIT_RPR_WORK'}) or die;
277 defined($destrepo = $ENV{'DGIT_RPR_DEST'}) or die;
278 defined($keyrings = $ENV{'DGIT_RPR_KEYRINGS'}) or die $!;
279 open STDOUT, ">&STDERR" or die $!;
286 die if $ARGV[0] =~ m/^-/;
287 $ENV{'DGIT_RPR_KEYRINGS'} = shift @ARGV;
288 die if $ARGV[0] =~ m/^-/;
289 $dgitrepos = shift @ARGV;
292 if ($ARGV[0] != m/^-/) {
295 } elsif ($ARGV[0] eq '--ssh') {
298 my $cmd = $ENV{'SSH_ORIGINAL_COMMAND'};
302 (?:dgit-repos-push-receiver|git-receive-pack)
308 or die "requested command $cmd not understood";
314 $destrepo = "$dgitrepos/$pkg.git";
321 runcmd qw(git receive-pack), $destdir;