Add initial configuration support to gatekeeper
authorJacob Bachmeyer <jcb@gnu.org>
Thu, 23 Mar 2023 01:50:00 +0000 (20:50 -0500)
committerJacob Bachmeyer <jcb@gnu.org>
Thu, 23 Mar 2023 01:50:00 +0000 (20:50 -0500)
gatekeeper.pl

index 62a78a8b023786198cbb15733e1d15eb96b94e5e..5a9c79f4f0902d85cf6bfa8789c30a4376abb7dc 100755 (executable)
@@ -26,9 +26,9 @@ gatekeeper - select properly signed uploads and move them into place
 
 =head1 SYNOPSIS
 
-gatekeeper.pl -B<z> I<zone> [-B<d> I<debuglevel>]
+gatekeeper.pl [-B<c> I<config>] [-B<z> I<zone>] [-B<d> I<level>]
 
-gatekeeper.pl --B<zone> I<zone> [--B<debug> I<debuglevel>]
+gatekeeper.pl [--B<conf> I<config>] [--B<zone> I<zone>] [--B<debug> I<level>]
 
 gatekeeper.pl --B<version>
 
@@ -46,6 +46,15 @@ Show usage information and exit.
 
 Show version information and exit.
 
+=item B<--conf> I<file>
+
+=item B<--config> I<file>
+
+=item B<--configfile> I<file>
+
+Specify alternate configuration file.  Default is C<gatekeeper.conf> in the
+same directory as this tool.
+
 =item B<--zone>
 
 Specify the zone to process.  The "zone" selects a configuration subset for
@@ -243,6 +252,7 @@ BEGIN {
   my $want_help = '';
   my $want_version = '';
 
+  my $ConfigFile = File::Spec->catfile($FindBin::Bin, 'gatekeeper.conf');
   my $GPGV_Bin = '/usr/bin/gpgv';
   my $LSOF_Bin = '/usr/bin/lsof';
 
@@ -256,9 +266,11 @@ BEGIN {
   my $TSTAMPCHECK = 1;
 
   my $TestingMode = 0;
+  my $CheckConfigurationParse = 0;
 
   GetOptions('help' => \$want_help,
             'version' => \$want_version,
+            'configfile|config|conf|c=s' => \$ConfigFile,
             'zone|z|s=s' => \$ZONE,
             'with-gpgv=s' => \$GPGV_Bin,
             'with-lsof=s' => \$LSOF_Bin,
@@ -266,6 +278,7 @@ BEGIN {
             'nomail=i' => \$NOMAIL,
             'debug|d=i' => \$DEBUG,
             'testing-this-script' => \$TestingMode,
+            'check-config-parse' => \$CheckConfigurationParse,
            ) or pod2usage(-verbose => 0, -exitval => 2);
 
   constant->import(ZONE => $ZONE);
@@ -292,6 +305,164 @@ BEGIN {
     constant->import(GPGV_BIN => $GPGV_Bin);
     constant->import(LSOF_BIN => $LSOF_Bin);
   }
+
+  # Read the configuration file.
+  unless ($want_help || $want_version) {
+    # --help and --version should work even without a configuration file
+
+    if ($TestingMode) {          # use hardwired test configuration
+      # Again, the test environment is trusted, but we still run in taint mode.
+      $ENV{TEST_BASE_DIR} =~ m/^([[:graph:] ]+$)/ && -d $1
+       or die "gatekeeper: test mode: TEST_BASE_DIR not valid";
+      my $base = $1;           # untainted
+
+      our $email_blacklist   = File::Spec->catfile($base, 'email.blacklist');
+      our $maintainers_bypkg = File::Spec->catfile($base, 'm.bypkg');
+
+      our $Public_Upload_Archive_Inbox = 'ftp-upload-report@gnu.org';
+      our $Internal_Report_Inbox = 'ftp-upload-script@gnu.org';
+
+      our $zone_tag = 'ftp';
+      our $Log_Tag  = 'Test';
+
+      our $package_config_base =File::Spec->catdir($base, 'packages');
+      our $package_state_base = $base;
+      our $serials_path           =    File::Spec->catfile($base, 'serial.txt');
+
+      our $Inbox_dir   =       File::Spec->catdir($base, 'inbox');
+      our $Scratch_dir =       File::Spec->catdir($base, 'scratch');
+      our $Stage_dir   =       File::Spec->catdir($base, 'stage');
+      our $Public_dir  =       File::Spec->catdir($base, 'pub');
+      our $Archive_dir =       File::Spec->catdir($base, 'archive');
+    } else {           # load configuration from file
+      my @zonelist = ();
+      my %ZoneConfig = ();
+      my %EmailConfig = ();
+
+      open my $config, '<', $ConfigFile
+       or die "gatekeeper: read config $ConfigFile: $!\n";
+
+      my $interesting = $ZONE ? undef : \%ZoneConfig;
+      while (<$config>) {
+       chomp;
+       next if m/^$/ || m/^\s*#/; # skip blank lines and comments
+       # collect configured zone names
+       push @zonelist, $1 if m/^\[zone[. ]([-_.[:alnum:]]+)\]/;
+
+       if (m/^\[([-_.[:alnum:]]*)\]/) {
+         # begin section
+         if ($1 eq 'email')            { $interesting = \%EmailConfig }
+         elsif ($1 eq 'zone.'.$ZONE)   { $interesting = \%ZoneConfig }
+         else                          { $interesting = undef }
+       } elsif (not defined $interesting) {
+         next
+       } elsif (m/^([-_.[:alnum:]]+)\s*=\s*(.*)$/) {
+         # store configuration option
+         $interesting->{$1} = $2;
+       } else
+         { die "gatekeeper: unrecognized configuration line $.: $_\n" }
+      }
+
+      close $config;
+
+      # Check if zones are configured and/or --zone given.
+      if (@zonelist and $ZONE eq '') {
+       die "gatekeeper: zones configured but --zone parameter not given\n"
+         .join(' ', 'gatekeeper: known zones:', @zonelist)."\n";
+      } elsif ($ZONE and not grep $_ eq $ZONE, @zonelist) {
+       die "gatekeeper: requested zone '$ZONE' not configured\n"
+         .join(' ', 'gatekeeper: known zones:', @zonelist)."\n";
+      }
+
+      our $email_blacklist = $EmailConfig{blacklist};
+      our $maintainers_bypkg = $EmailConfig{maintainermap};
+
+      our $Public_Upload_Archive_Inbox = $EmailConfig{archivebox};
+      our $Internal_Report_Inbox = $EmailConfig{internalbox};
+
+      our $zone_tag = $ZoneConfig{tag} || $ZONE || 'upload';
+      our $Log_Tag = $ZoneConfig{logtag} || ucfirst $ZONE || 'Upload';
+
+      our $package_config_base = $ZoneConfig{pkgconfdir};
+      our $package_state_base = $ZoneConfig{pkgstatedir};
+      our $serials_path;
+      $serials_path= File::Spec->catfile($ZoneConfig{pkgstatedir},
+                                        $ZoneConfig{serials})
+       if $ZoneConfig{pkgstatedir} && $ZoneConfig{serials};
+
+      our $Inbox_dir = $ZoneConfig{inboxdir};
+      our $Scratch_dir = $ZoneConfig{scratchdir};
+      our $Stage_dir = $ZoneConfig{stagedir};
+      our $Public_dir = $ZoneConfig{publicdir};
+      our $Archive_dir = $ZoneConfig{archivedir};
+    }
+
+    if ($CheckConfigurationParse) {
+      our $email_blacklist; our $maintainers_bypkg;
+      our $Public_Upload_Archive_Inbox; our $Internal_Report_Inbox;
+      our $zone_tag; our $Log_Tag; our $serials_path;
+      our $package_config_base; our $package_state_base;
+      our $Inbox_dir; our $Scratch_dir;
+      our $Stage_dir; our $Public_dir; our $Archive_dir;
+
+      if ($ZONE) {
+       print "# gatekeeper configuration as parsed for zone $ZONE:\n\n";
+      } else {
+       print "# gatekeeper configuration as parsed:\n\n";
+      }
+      print "[zone.$ZONE]\n" if $ZONE;
+      foreach my $item ([tag => $zone_tag], [logtag => $Log_Tag],
+                       [pkgconfdir => $package_config_base],
+                       [pkgstatedir => $package_state_base],
+                       [inboxdir => $Inbox_dir], [scratchdir => $Scratch_dir],
+                       [stagedir => $Stage_dir], [publicdir => $Public_dir],
+                       [archivedir => $Archive_dir])
+       { print $item->[0],' = ',$item->[1],"\n" if $item->[1] }
+      if ($email_blacklist || $maintainers_bypkg
+         || $Public_Upload_Archive_Inbox || $Internal_Report_Inbox) {
+       print "\n[email]\n";
+       print "blacklist = $email_blacklist\n" if $email_blacklist;
+       print "maintainermap = $maintainers_bypkg\n" if $maintainers_bypkg;
+       print "\n"
+         if ($email_blacklist || $maintainers_bypkg)
+           && ($Public_Upload_Archive_Inbox || $Internal_Report_Inbox);
+       print "archivebox = $Public_Upload_Archive_Inbox\n"
+         if $Public_Upload_Archive_Inbox;
+       print "internalbox = $Internal_Report_Inbox\n"
+         if $Internal_Report_Inbox;
+      }
+      print "\n# END\n";
+    }
+
+    # Verify that all required configuration parameters are set
+    {
+      our $email_blacklist; our $maintainers_bypkg;
+      our $Public_Upload_Archive_Inbox; our $Internal_Report_Inbox;
+      our $zone_tag; our $Log_Tag; our $serials_path;
+      our $package_config_base; our $package_state_base;
+      our $Inbox_dir; our $Scratch_dir;
+      our $Stage_dir; our $Public_dir; our $Archive_dir;
+
+      my $ok = 1;
+
+      foreach my $item ([pkgconfdir => $package_config_base],
+                       [pkgstatedir => $package_state_base],
+                       [inboxdir => $Inbox_dir], [scratchdir => $Scratch_dir],
+                       [stagedir => $Stage_dir], [publicdir => $Public_dir],
+                       [archivedir => $Archive_dir]) {
+       unless ($item->[1]) {
+         $ok = 0;
+         print "gatekeeper: configuration parameter not set: $item->[0]\n";
+       }
+      }
+
+      die "gatekeeper: required configuration parameter(s) not set\n"
+       unless $ok;
+    }
+
+    exit 0 if $CheckConfigurationParse;
+
+  } # end of configuration handling skipped for --help and --version
 }
 
 if (WANT_VERSION) {
@@ -304,74 +475,19 @@ if (WANT_VERSION) {
 }
 
 pod2usage(-verbose => 1, -exitval => 0) if WANT_HELP;
-pod2usage(-message => 'ERROR:  Required parameter not given or invalid.',
-         -verbose => 0, -exitval => 2)
-  if ((ZONE ne 'ftp') && (ZONE ne 'alpha') && (ZONE ne 'distros'));
-
-my $zone_tag = 'ftp';
-$zone_tag = 'alpha' if (ZONE eq 'alpha');
-$zone_tag = 'gnu+linux-distros' if (ZONE eq 'distros');
-
-# Settings to configure:
-my $package_config_base = "/home/gatekpr/packages";
-{
-  # where ftpd deposits the files for us to look at:
-  our $Inbox_dir = "/home/upload/incoming/$zone_tag";
-  # private dir on SAME FILESYSTEM as $Inbox_dir:
-  our $Scratch_dir = "/var/tmp/$zone_tag-in";
-  # top-level public ftp dir for installing files:
-  our $Public_dir = "/home/$zone_tag/gnu";
-  $Public_dir = "/home/ftp/$zone_tag"
-    if ($zone_tag eq 'gnu+linux-distros'); # The distros go here
-  # private dir on SAME FILESYSTEM as $Public_dir:
-  our $Archive_dir = "/home/gatekpr/$zone_tag-archived";
-  # private dir on SAME FILESYSTEM as $Public_dir:
-  our $Stage_dir = "/var/tmp/$zone_tag-out";
-}
-
-# We sometimes want to exclude e-mail addresses from being emailed.
-# Specifically, e-mail addresses we import from gpg keys - keys are still
-# valid but associated e-mail addresses are not. Ward, 2011-02-08.
-my $email_blacklist = "/home/gatekpr/etc/email_blacklist";
-
-# List of all package maintainers
-my $maintainers_bypkg = "/home/gatekpr/etc/maintainers.bypkg";
-
-# E-mail addresses
-{
-  # - public archive of successful upload reports and some errors
-  our $Public_Upload_Archive_Inbox = 'ftp-upload-report@gnu.org';
-  # - unmoderated collector of other errors and copies of some reports
-  our $Internal_Report_Inbox = 'ftp-upload-script@gnu.org';
-}
-
-my $serials_path = "/home/gatekpr/etc/upload-ftp-serials.txt";
 
+# temporary scaffolding
+our $zone_tag;
+our $package_config_base;
+our $email_blacklist;
+our $maintainers_bypkg;
+our $serials_path;
 
 # syslog destination
 use constant SYSLOG_APP_IDENT => 'gatekeeper';
 use constant SYSLOG_FACILITY => 'LOCAL5';
 
-if (IN_TEST_MODE) {    # override the above for testing
-  # override file paths to our testcase environment
-  {
-    # Again, the test environment is trusted, but we still run in taint mode.
-    $ENV{TEST_BASE_DIR} =~ m/^([[:graph:] ]+$)/ && -d $1
-      or die "gatekeeper: test mode: TEST_BASE_DIR not valid";
-    my $base = $1; # untainted
-
-    $package_config_base =     File::Spec->catdir($base, 'packages');
-
-    our $Inbox_dir     =       File::Spec->catdir($base, 'inbox');
-    our $Scratch_dir   =       File::Spec->catdir($base, 'scratch');
-    our $Stage_dir     =       File::Spec->catdir($base, 'stage');
-    our $Public_dir    =       File::Spec->catdir($base, 'pub');
-    our $Archive_dir   =       File::Spec->catdir($base, 'archive');
-
-    $email_blacklist   =       File::Spec->catfile($base, 'email.blacklist');
-    $maintainers_bypkg =       File::Spec->catfile($base, 'm.bypkg');
-    $serials_path      =       File::Spec->catfile($base, 'serial.txt');
-  }
+if (IN_TEST_MODE) {
   # verify mock gpgv
   {
     open my $gpgv,'-|',GPGV_BIN, '--version'
@@ -411,17 +527,6 @@ if (IN_TEST_MODE) {        # override the above for testing
 =cut
 
 {
-  # To identify which zone is being processed, ftp_syslog will prepend
-  # this, inside parentheses, to all messages logged.
-  if (IN_TEST_MODE) {
-    # override log message tag
-    our $Log_Tag = 'Test';
-  } else {
-    our $Log_Tag = 'GNU';
-    $Log_Tag = 'Alpha' if (ZONE eq 'alpha');
-    $Log_Tag = 'Distros' if (ZONE eq 'distros');
-  }
-
   # If this is set to a defined value, ftp_syslog will prepend it, inside
   # square brackets, to all messages logged.
   our $Phase = undef;          # should be set using local