From a6c2ecc6b5deefe8b8d3afa3d25fa15df40daaaf Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Thu, 4 Mar 2021 00:32:06 -0700 Subject: [PATCH] toolbox/sanity-check.pl: sanity checking tool Add sanity-check.pl tool that performs basic sanity checking on users and projects displaying an abbreviated summary of any issues uncovered for each user and project checked. Signed-off-by: Kyle J. McKay --- toolbox/sanity-check.pl | 225 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100755 toolbox/sanity-check.pl diff --git a/toolbox/sanity-check.pl b/toolbox/sanity-check.pl new file mode 100755 index 0000000..75a8b69 --- /dev/null +++ b/toolbox/sanity-check.pl @@ -0,0 +1,225 @@ +#!/usr/bin/perl + +# sanity-check.pl - perform basic sanity checks +# Copyright (C) 2021 Kyle J. McKay. +# All rights reserved. +# License GPLv2+: GNU GPL version 2 or later. +# www.gnu.org/licenses/gpl-2.0.html +# This is free software: you are free to change and redistribute it. +# There is NO WARRANTY, to the extent permitted by law. + +use strict; +use warnings; +use vars qw($VERSION); +BEGIN {*VERSION = \'1.0.0'} +use Scalar::Util qw(looks_like_number); +use File::Basename qw(basename); +use lib "__BASEDIR__"; +use Girocco::Config; +use Girocco::Util; +use Girocco::CLIUtil; +use Girocco::User; +use Girocco::Project; +my $bn; BEGIN {$bn = basename(__FILE__)} + +exit(&main(@ARGV)||0); + +our $help; +BEGIN {$help = <<'HELP'} +Usage: %s [--help] + --help show this help + -P/--progress show progress on STDERR (default if STDERR is a tty) + + Exit status will always be non-zero if any issues are detected. +HELP + +my $show_progress; +my $progress; +END {$progress = undef} +my $errs; + +sub ProgressInit { + my ($max, $title) = @_; + $show_progress or $max = 0; + if (ref($progress)) { + $progress->reset($max, $title); + } else { + $progress = Girocco::CLIUtil::Progress->new($max, $title); + } + $progress; +} + +my %users; + +sub check_users { + %users = map({($$_[0] => $_)} Girocco::User::get_full_list_extended()); + my @users = sort({lc($a) cmp lc($b) || $a cmp $b} keys(%users)); + ProgressInit(scalar(@users), "Checking users"); + foreach (qw(everyone mob git)) { + exists($users{$_}) or $progress->emit("user: $_: missing"); + } + my $cnt = 0; + foreach (@users) { + my @f = @{$users{$_}}; + $f[0] eq $_ or die "programmer error"; + $f[2] >= 65536 or next; + my @p = (); + @f == 7 or push(@p, "not-7-fields"); + looks_like_number($f[3]) && $f[3] == int($f[3]) or do { + push(@p, "bad-group-num"); + $f[3] = 0; + }; + defined($f[4]) or $f[4] = ''; + defined($f[5]) or $f[5] = ''; + defined($f[6]) or $f[6] = ''; + $f[4] ne "" or push(@p, "empty-desc"); + $f[5] eq "/" or push(@p, "bad-home"); + my $sk = jailed_file('/etc/sshkeys/'.$_); + -f $sk or push(@p, "missing-sshkeys-file"); + if ($_ eq 'mob') { + -f $sk && ! -s _ or push(@p, "sshkeys-not-empty"); + $f[1] eq "" or push(@p, "pw-not-empty"); + $f[3] == $Girocco::Config::var_group_gid or push(@p, "wrong-gid"); + $f[6] eq "/bin/git-shell-verify" or push(@p, "wrong-shell"); + } elsif ($_ eq 'git') { + -f $sk && ! -s _ or push(@p, "sshkeys-not-empty"); + $f[1] eq "" or push(@p, "pw-not-empty"); + $f[3] != $Girocco::Config::var_group_gid or push(@p, "wrong-gid"); + $f[6] eq "/bin/git-shell-verify" or push(@p, "wrong-shell"); + } elsif ($_ eq 'everyone') { + -f $sk && ! -s _ or push(@p, "sshkeys-not-empty"); + length($f[1]) == 1 or push(@p, "pw-not-disabled"); + $f[3] == $Girocco::Config::var_group_gid or push(@p, "wrong-gid"); + $f[6] eq "/bin/false" or push(@p, "wrong-shell"); + } else { + $_ eq "root" and push(@p, "wrong-uid"); + $_ eq $Girocco::Config::mirror_user and push(@p, "wrong-uid"); + length($f[1]) == 1 or push(@p, "pw-not-disabled"); + $f[3] == $Girocco::Config::var_group_gid or push(@p, "wrong-gid"); + $f[6] eq "/bin/git-shell-verify" or push(@p, "wrong-shell"); + my @g = split(/,/, $f[4], -1); + @g <= 3 or push(@p, "excess-desc-fields"); + defined($g[1]) or $g[1] = ''; + defined($g[2]) or $g[2] = ''; + if ($g[0] eq "") { + push(@p, "missing-email"); + } else { + my @e = split(/[@]/, $g[0], 2); + defined($e[1]) or $e[1] = ''; + $e[0] ne "" or push(@p, "missing-email-user"); + $e[1] ne "" or push(@p, "missing-email-host"); + $e[0] ne "" && $e[1] ne "" && valid_email($g[0]) or + push(@p, "email-invalid"); + } + if ($g[1] ne "") { + $g[1] =~ /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/ or + push(@p, "uuid-invalid"); + } + if ($g[2] ne "") { + $g[2] =~ /^\d{8}_\d{6}$/ or push(@p, "creation-date-invalid"); + } + eval { + my $u = Girocco::User->load($_); + ref($u); + } or push(@p, "unloadable"); + } + @p and ++$errs; + @p and $progress->emit("user: $_: ".join(" ", @p)); + } continue {$progress->update(++$cnt)} +} + +my %projects; + +sub check_projects { + %projects = map({($$_[0] => $_)} Girocco::Project::get_full_list_extended()); + my @projects = sort({lc($a) cmp lc($b) || $a cmp $b} keys(%projects)); + ProgressInit(scalar(@projects), "Checking projects"); + my $bd = $Girocco::Config::reporoot . '/'; + my $cnt = 0; + foreach (@projects) { + my @f = @{$projects{$_}}; + $f[0] eq $_ or die "programmer error"; + $f[2] >= 65536 or next; + my @p = (); + @f == 4 || @f == 5 or push(@p, "not-4-or-5-fields"); + @f >= 5 && $f[4] ne "" and push(@p, "field-4-not-empty"); + $f[1] ne "" or push(@p, "pw-empty"); + if ($f[3] ne "") { + my $badusers = 0; + my $unknownusers = 0; + my @u = split(/,/, $f[3], -1); + foreach my $u (@u) { + if ($u =~ /^[a-zA-Z0-9][a-zA-Z0-9+._-]*$/) { + exists($users{$u}) or ++$unknownusers; + } else { + ++$badusers; + } + } + $badusers && push(@p, "bad-users"); + $unknownusers && push(@p, "unknown-users"); + } + my $pd = $bd . $_ . '.git'; + if (! -d $pd) { + push(@p, "missing-gitdir"); + } else { + if (@f == 4 || @f == 5) { + my $nofetch = -e "$pd/.nofetch"; + (@f == 4 && !$nofetch) || (@f == 5 && $nofetch) and + push(@p, "nofetch-fieldcnt-mismatch"); + } + my $p; + eval { $p = Girocco::Project->load($_) }; + if (!ref($p)) { + push(@p, "unloadable"); + } else { + my ($cnt, $err) = (0, ""); + my $origreadme = $p->{README}; + defined($origreadme) or $origreadme = ""; + if (! eval { ($cnt, $err) = $p->_lint_readme(0); 1 }) { + push(@p, "readmefmt-died"); + } else { + if ($cnt) { + push(@p, "readmefmt-failed"); + } else { + my $readme = $p->{README}; + defined($readme) or $readme = ""; + chomp $origreadme; + chomp $readme; + $origreadme eq $readme or + push(@p, "readmefmt-mismatch"); + } + } + } + } + @p and ++$errs; + @p and $progress->emit("project: $_: ".join(" ", @p)); + } continue {$progress->update(++$cnt)} +} + +sub dohelp { + my $fd = shift; + my $ec = shift; + printf $fd "%s version %s\n", $bn, $VERSION; + printf $fd $help, $bn; + exit $ec; +} + +sub main { + local *ARGV = \@_; + my $help; + my $progress = -t STDERR; + { + shift, $help=1, redo if @ARGV && $ARGV[0] =~ /^(?:-h|--help)$/i; + shift, $progress=1, redo if @ARGV && $ARGV[0] =~ /^(?:-P|--progress)$/i; + shift, $progress=0, redo if @ARGV && $ARGV[0] =~ /^(?:--no-progress)$/i; + } + !@ARGV && !$help or dohelp($help ? \*STDOUT : \*STDERR, !$help); + $show_progress = $progress; + $errs = 0; + check_users; + check_projects; + print "User-count: ".scalar(keys(%users)). + " Project-count: ".scalar(keys(%projects)). + " Issues-found: $errs\n"; + exit $errs ? 1 : 0; +} -- 2.11.4.GIT