chiark / gitweb /
Support --deliberately-not-fast-forward
[dgit.git] / infra / dgit-repos-server
index d2f94f1957e0bed922a0db052e2a81b921fa789a..551efff9fb5f466ec716dbd0d951fd83040443dd 100755 (executable)
@@ -103,6 +103,7 @@ our $keyrings;
 our @lockfhs;
 our $debug='';
 our @deliberatelies;
+our %supersedes;
 our $policy;
 
 #----- utilities -----
@@ -211,12 +212,24 @@ sub mkrepo_fromtemplate ($) {
 }
 
 sub movetogarbage () {
-    my $garbagerepo = "$dgitrepos/_tmp/${package}_garbage";
-    acquiretree($garbagerepo,1);
-    rmtree $garbagerepo;
+    my $garbagerepo = "$dgitrepos/${package}_garbage";
+    my $lfh =acquiretree($garbagerepo,1);
+    # We arrange to always keep at least one old tree, for anti-rewind
+    # purposes (and, I guess, recovery from mistakes).  This is either
+    # $garbage or $garbage-old.
+    if (stat_exists "$garbagerepo") {
+       rmtree "$garbagerepo-tmp";
+       if (rename "$garbagerepo-old", "$garbagerepo-tmp") {
+           rmtree "$garbagerepo-tmp";
+       } else {
+           die "$garbagerepo $!" unless $!==ENOENT;
+       }
+       rename "$garbagerepo", "$garbagerepo-old" or die "$garbagerepo $!";
+    }
     rename $realdestrepo, $garbagerepo
        or $! == ENOENT
-       or die "rename repo $realdestrepo to $garbagerepo: $!";
+       or die "$garbagerepo $!";
+    close $lfh;
 }
 
 sub onwardpush () {
@@ -391,6 +404,9 @@ sub parsetag () {
                    die "$1 != $distro" unless $1 eq $distro;
                } elsif (s/^(--deliberately-$package_re) //) {
                    push @deliberatelies, $1;
+               } elsif (s/^supersede:(\S+)=(\w+) //) {
+                   die "supersede $1 twice" if defined $supersedes{$1};
+                   $supersedes{$1} = $2;
                } elsif (s/^[-+.=0-9a-z]\S* //) {
                } else {
                    die "unknown dgit info in tag";
@@ -520,6 +536,74 @@ sub checksuite () {
     reject "unknown suite";
 }
 
+sub checktagnoreplay () {
+    # We check that the signed tag mentions the name and value of
+    # (a) in the case of FRESHREPO all tags in the repo;
+    # (b) in the case of just NOFFCHECK all tags referring to
+    # the current head for the suite (there must be at least one).
+    # This prevents a replay attack using an earlier signed tag.
+    return unless $policy & (FRESHREPO|NOFFCHECK);
+
+    my $garbagerepo = "$dgitrepos/${package}_garbage";
+    acquiretree($garbagerepo,1);
+
+    local $ENV{GIT_DIR};
+    foreach my $garb ("$garbagerepo", "$garbagerepo-old") {
+       if (stat_exists $garb) {
+           $ENV{GIT_DIR} = $garb;
+           last;
+       }
+    }
+    if (!defined $ENV{GIT_DIR}) {
+       # Nothing to overwrite so the FRESHREPO and NOFFCHECK were
+       # pointless.  Oh well.
+       debug "checktagnoreplay - no garbage, ok";
+       return;
+    }
+
+    my $onlyreferring;
+    if (!($policy & FRESHREPO)) {
+       my $branch = server_branch($suite);
+       $!=0; $?=0; $_ =
+           `git for-each-ref --format='%(objectname)' '[r]efs/$branch'`;
+       defined or die "$branch $? $!";
+       $? and die "$branch $?";
+       if (!length) {
+           # No such branch - NOFFCHECK was unnecessary.  Oh well.
+           debug "checktagnoreplay - not FRESHREPO, new branch, ok";
+           return;
+       }
+       m/^(\w+)\n$/ or die "$branch $_ ?";
+        $onlyreferring = $1;
+       debug "checktagnoreplay - not FRESHREPO,".
+           " checking for overwriting refs/$branch=$onlyreferring";
+    }
+
+    my @problems;
+
+    git_for_each_tag_referring($objreferring, sub {
+       my ($objid,$fullrefname,$tagname) = @_;
+       debug "checktagnoreplay - overwriting $fullrefname=$objid";
+       my $supers = $supersedes{$fullrefname};
+       if (!defined $supers) {
+           push @problems, "does not supersede $fullrefname";
+       } elsif ($supers ne $objid) {
+           push @problems,
+ "supersedes $fullrefname=$supers but previously $fullrefname=$objid";
+       } else {
+           # ok;
+       }
+    });
+
+    if (@problems) {
+       reject "replay attack prevention check failed:".
+           " signed tag for $version: ".
+           join("; ", @problems).
+           "\n";
+    }
+    debug "checktagnoreply - all ok"
+}
+
 sub tagh1 ($) {
     my ($tag) = @_;
     my $vals = $tagh{$tag};
@@ -545,6 +629,7 @@ sub checks () {
                         $version,$suite,$tagname,
                         join(",",@delberatelies));
 
+    checktagnoreplay();
     checksuite();
 
     # check that our ref is being fast-forwarded