From 497a8ba2cf6583a88f34159207ab703ec4fad492 Mon Sep 17 00:00:00 2001 From: Jacob Bachmeyer Date: Wed, 22 Mar 2023 20:50:00 -0500 Subject: [PATCH] Add initial configuration support to gatekeeper --- gatekeeper.pl | 255 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 180 insertions(+), 75 deletions(-) diff --git a/gatekeeper.pl b/gatekeeper.pl index 62a78a8..5a9c79f 100755 --- a/gatekeeper.pl +++ b/gatekeeper.pl @@ -26,9 +26,9 @@ gatekeeper - select properly signed uploads and move them into place =head1 SYNOPSIS -gatekeeper.pl -B I [-B I] +gatekeeper.pl [-B I] [-B I] [-B I] -gatekeeper.pl --B I [--B I] +gatekeeper.pl [--B I] [--B I] [--B I] gatekeeper.pl --B @@ -46,6 +46,15 @@ Show usage information and exit. Show version information and exit. +=item B<--conf> I + +=item B<--config> I + +=item B<--configfile> I + +Specify alternate configuration file. Default is C 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 -- 2.25.1