Use new slurp_directive_file helper in read_directive_file
authorJacob Bachmeyer <jcb@gnu.org>
Wed, 19 Oct 2022 02:03:45 +0000 (21:03 -0500)
committerJacob Bachmeyer <jcb@gnu.org>
Wed, 19 Oct 2022 02:03:45 +0000 (21:03 -0500)
This also ensures that unsigned lines in directives are consistently
ignored.  The testsuite is adjusted accordingly; this resolves an issue
with consistently detecting and rejecting unsigned directives.

gatekeeper.pl
testsuite/gatekeeper.all/01_loose.exp
testsuite/lib/gatekeeper.exp

index 8549e82f1fd2d59274dc8031deadbd0dcf4dee71..5ee31bbba62097dcf3870241fd36a24919b0d587 100755 (executable)
@@ -884,6 +884,50 @@ sub read_directive_from_string {
   return $records;
 }
 
+=item $text = slurp_directive_file ( $filename )
+
+Read the first PGP-clearsigned message from the file FILENAME and return
+it, complete with all headers and the full signature block.
+
+The returned string is tainted.
+
+=cut
+
+sub slurp_directive_file {
+  my $filename = shift;
+
+  local *_;
+  my @lines;
+
+  # Note that the loops below preserve line endings.
+
+  open my $handle, '<', $filename
+    or die "open($filename) failed: $!";
+  # First, we find the PGP signature headers.
+  while (<$handle>) {
+    last if m/^-----BEGIN PGP SIGNED MESSAGE-----\s*\r*\n$/;
+    # RFC4880 allows trailing whitespace on marker lines.
+  }
+  return '' unless defined $_; # no signed message at all?
+  @lines = ($_);               # store the header
+  # We are now in the armor headers.
+  while (<$handle>) {
+    push @lines, $_;
+    # According to RFC4880, there must be exactly one empty line to
+    # separate the signed message from the armor headers.
+    last if m/^$/;
+  }
+  # We are now looking at the signed message text and signature.
+  while (<$handle>) {
+    push @lines, $_;
+    last if m/^-----END PGP SIGNATURE-----\s*\r*\n$/;
+  }
+  close $handle
+    or die "close($filename) failed: $!";
+
+  return join('', @lines);
+}
+
 =item @values = find_directive_elements ( $directive, $key )
 
 Search the DIRECTIVE arrayref for KEY elements and return their associated
@@ -1420,16 +1464,15 @@ sub read_directive_file {
   my $uploaded_file = shift;
   my $directive_only = shift;
 
-  # For debugging purposes, see below
-  my $directive_file_contents = '';
-
-  open DIRECTIVE_FILE, '<', $directive_file
-    or ftp_abort("FATAL: open($directive_file) failed: $!");
-  $directive_file_contents = join('', <DIRECTIVE_FILE>);
-  close DIRECTIVE_FILE
-    or ftp_warn("close($directive_file) failed: $!");
+  my $directive_file_contents = slurp_directive_file($directive_file);
   my $directive = read_directive_from_string($directive_file_contents);
 
+  if ($directive_file_contents eq '') {
+    # This implies that the directive file did not contain a signed
+    # message.  There is nothing further to do.
+    fatal("directive file $directive_file has no signature",0)
+  }
+
   # If we don't know whose project this file belongs to, because the
   # 'directory:' line is messed up or not there, we'd still like to let the
   # uploader know something went wrong.  So let's see if we can match the
index 17e4a2b9350a8bec2bfc344d7d01f302604b9c15..b844f8ba1f2f47ab38be0cf4641ebc2c8b2ca9ee 100644 (file)
@@ -95,10 +95,6 @@ proc check_loose_directive { desc case args } {
 
 # ----------------------------------------
 
-# TODO: All of the unsigned directive tests should probably produce a
-#      message about the lack of a signature or a failed signature check
-#      at the least; currently, they do not consistently do so.
-
 check_loose_directive "bogus: unsigned with no directory specified" {
     directive {
        version 1.2
@@ -108,6 +104,8 @@ check_loose_directive "bogus: unsigned with no directory specified" {
 } file-tree {
     { incoming stage pub archive } empty {}
     { in-stage } files { foo.directive.asc }
+} log {
+    unsigned-directive,foo.directive.asc "unsigned directive"
 } email-to {
     ftp-upload-script@gnu.org
 }
@@ -123,7 +121,7 @@ check_loose_directive "bogus: unsigned for bogus package" {
     { incoming stage pub archive } empty {}
     { in-stage } files { foo.directive.asc }
 } log {
-    unknown-package "unknown package from directive"
+    unsigned-directive,foo.directive.asc "unsigned directive"
 } email-to {
     ftp-upload-script@gnu.org
 }
@@ -138,6 +136,8 @@ check_loose_directive "bogus: unsigned for package with no email address" {
 } file-tree {
     { incoming stage pub archive } empty {}
     { in-stage } files { foo.directive.asc }
+} log {
+    unsigned-directive,foo.directive.asc "unsigned directive"
 } email-to {
     ftp-upload-script@gnu.org
 }
@@ -152,8 +152,10 @@ check_loose_directive "bogus: unsigned for valid package" {
 } file-tree {
     { incoming stage pub archive } empty {}
     { in-stage } files { foo.directive.asc }
+} log {
+    unsigned-directive,foo.directive.asc "unsigned directive"
 } email-to {
-    ftp-upload-script@gnu.org foo@example.org foo@example.net
+    ftp-upload-script@gnu.org
 }
 
 check_loose_directive "bogus: signed with no directory specified" {
index b6f8e0521834358a9221fce7189b786ecb0ef361..da41226a20a4824038a53782b70ed44ddfc55d65 100644 (file)
@@ -640,6 +640,13 @@ proc analyze_log { base_dir name assess } {
                     exp_continue
                 }
 
+       -re {^gatekeeper\[[0-9]+\]: \(Test\)\
+                directive file ([^ ]+) has no signature} {
+                    # from read_directive_file, when signed message is null
+                    set A(unsigned-directive) 1
+                    set A(unsigned-directive,$expect_out(1,string)) 1
+                    exp_continue
+                }
        -re {^gatekeeper\[[0-9]+\]: \(Test\)\
                 invalid directive 'replace', not supported[^\r\n]+} {
                     # from read_directive_file, if replace used in v1.1