Blame shasum

Packit fa4fcc
#!perl
Packit fa4fcc
Packit fa4fcc
	## shasum: filter for computing SHA digests (ref. sha1sum/md5sum)
Packit fa4fcc
	##
Packit fa4fcc
	## Copyright (C) 2003-2018 Mark Shelor, All Rights Reserved
Packit fa4fcc
	##
Packit fa4fcc
	## Version: 6.02
Packit fa4fcc
	## Fri Apr 20 16:25:30 MST 2018
Packit fa4fcc
Packit fa4fcc
	## shasum SYNOPSIS adapted from GNU Coreutils sha1sum. Add
Packit fa4fcc
	## "-a" option for algorithm selection,
Packit fa4fcc
	## "-U" option for Universal Newlines support, and
Packit fa4fcc
	## "-0" option for reading bit strings.
Packit fa4fcc
Packit fa4fcc
BEGIN { pop @INC if $INC[-1] eq '.' }
Packit fa4fcc
Packit fa4fcc
use strict;
Packit fa4fcc
use warnings;
Packit fa4fcc
use Fcntl;
Packit fa4fcc
use Getopt::Long;
Packit fa4fcc
use Digest::SHA qw($errmsg);
Packit fa4fcc
Packit fa4fcc
my $POD = <<'END_OF_POD';
Packit fa4fcc
Packit fa4fcc
=head1 NAME
Packit fa4fcc
Packit fa4fcc
shasum - Print or Check SHA Checksums
Packit fa4fcc
Packit fa4fcc
=head1 SYNOPSIS
Packit fa4fcc
Packit fa4fcc
 Usage: shasum [OPTION]... [FILE]...
Packit fa4fcc
 Print or check SHA checksums.
Packit fa4fcc
 With no FILE, or when FILE is -, read standard input.
Packit fa4fcc
Packit fa4fcc
   -a, --algorithm   1 (default), 224, 256, 384, 512, 512224, 512256
Packit fa4fcc
   -b, --binary      read in binary mode
Packit fa4fcc
   -c, --check       read SHA sums from the FILEs and check them
Packit fa4fcc
       --tag         create a BSD-style checksum
Packit fa4fcc
   -t, --text        read in text mode (default)
Packit fa4fcc
   -U, --UNIVERSAL   read in Universal Newlines mode
Packit fa4fcc
                         produces same digest on Windows/Unix/Mac
Packit fa4fcc
   -0, --01          read in BITS mode
Packit fa4fcc
                         ASCII '0' interpreted as 0-bit,
Packit fa4fcc
                         ASCII '1' interpreted as 1-bit,
Packit fa4fcc
                         all other characters ignored
Packit fa4fcc
Packit fa4fcc
 The following five options are useful only when verifying checksums:
Packit fa4fcc
       --ignore-missing  don't fail or report status for missing files
Packit fa4fcc
   -q, --quiet           don't print OK for each successfully verified file
Packit fa4fcc
   -s, --status          don't output anything, status code shows success
Packit fa4fcc
       --strict          exit non-zero for improperly formatted checksum lines
Packit fa4fcc
   -w, --warn            warn about improperly formatted checksum lines
Packit fa4fcc
Packit fa4fcc
   -h, --help        display this help and exit
Packit fa4fcc
   -v, --version     output version information and exit
Packit fa4fcc
Packit fa4fcc
 When verifying SHA-512/224 or SHA-512/256 checksums, indicate the
Packit fa4fcc
 algorithm explicitly using the -a option, e.g.
Packit fa4fcc
Packit fa4fcc
   shasum -a 512224 -c checksumfile
Packit fa4fcc
Packit fa4fcc
 The sums are computed as described in FIPS PUB 180-4.  When checking,
Packit fa4fcc
 the input should be a former output of this program.  The default
Packit fa4fcc
 mode is to print a line with checksum, a character indicating type
Packit fa4fcc
 (`*' for binary, ` ' for text, `U' for UNIVERSAL, `^' for BITS),
Packit fa4fcc
 and name for each FILE.  The line starts with a `\' character if the
Packit fa4fcc
 FILE name contains either newlines or backslashes, which are then
Packit fa4fcc
 replaced by the two-character sequences `\n' and `\\' respectively.
Packit fa4fcc
Packit fa4fcc
 Report shasum bugs to mshelor@cpan.org
Packit fa4fcc
Packit fa4fcc
=head1 DESCRIPTION
Packit fa4fcc
Packit fa4fcc
Running I<shasum> is often the quickest way to compute SHA message
Packit fa4fcc
digests.  The user simply feeds data to the script through files or
Packit fa4fcc
standard input, and then collects the results from standard output.
Packit fa4fcc
Packit fa4fcc
The following command shows how to compute digests for typical inputs
Packit fa4fcc
such as the NIST test vector "abc":
Packit fa4fcc
Packit fa4fcc
	perl -e "print qq(abc)" | shasum
Packit fa4fcc
Packit fa4fcc
Or, if you want to use SHA-256 instead of the default SHA-1, simply say:
Packit fa4fcc
Packit fa4fcc
	perl -e "print qq(abc)" | shasum -a 256
Packit fa4fcc
Packit fa4fcc
Since I<shasum> mimics the behavior of the combined GNU I<sha1sum>,
Packit fa4fcc
I<sha224sum>, I<sha256sum>, I<sha384sum>, and I<sha512sum> programs,
Packit fa4fcc
you can install this script as a convenient drop-in replacement.
Packit fa4fcc
Packit fa4fcc
Unlike the GNU programs, I<shasum> encompasses the full SHA standard by
Packit fa4fcc
allowing partial-byte inputs.  This is accomplished through the BITS
Packit fa4fcc
option (I<-0>).  The following example computes the SHA-224 digest of
Packit fa4fcc
the 7-bit message I<0001100>:
Packit fa4fcc
Packit fa4fcc
	perl -e "print qq(0001100)" | shasum -0 -a 224
Packit fa4fcc
Packit fa4fcc
=head1 AUTHOR
Packit fa4fcc
Packit fa4fcc
Copyright (C) 2003-2018 Mark Shelor <mshelor@cpan.org>.
Packit fa4fcc
Packit fa4fcc
=head1 SEE ALSO
Packit fa4fcc
Packit fa4fcc
I<shasum> is implemented using the Perl module L<Digest::SHA>.
Packit fa4fcc
Packit fa4fcc
=cut
Packit fa4fcc
Packit fa4fcc
END_OF_POD
Packit fa4fcc
Packit fa4fcc
my $VERSION = "6.02";
Packit fa4fcc
Packit fa4fcc
sub usage {
Packit fa4fcc
	my($err, $msg) = @_;
Packit fa4fcc
Packit fa4fcc
	$msg = "" unless defined $msg;
Packit fa4fcc
	if ($err) {
Packit fa4fcc
		warn($msg . "Type shasum -h for help\n");
Packit fa4fcc
		exit($err);
Packit fa4fcc
	}
Packit fa4fcc
	my($USAGE) = $POD =~ /SYNOPSIS(.+?)^=/sm;
Packit fa4fcc
	$USAGE =~ s/^\s*//;
Packit fa4fcc
	$USAGE =~ s/\s*$//;
Packit fa4fcc
	$USAGE =~ s/^ //gm;
Packit fa4fcc
	print $USAGE, "\n";
Packit fa4fcc
	exit($err);
Packit fa4fcc
}
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Sync stdout and stderr by forcing a flush after every write
Packit fa4fcc
Packit fa4fcc
select((select(STDOUT), $| = 1)[0]);
Packit fa4fcc
select((select(STDERR), $| = 1)[0]);
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Collect options from command line
Packit fa4fcc
Packit fa4fcc
my ($alg, $binary, $check, $text, $status, $quiet, $warn, $help);
Packit fa4fcc
my ($version, $BITS, $UNIVERSAL, $tag, $strict, $ignore_missing);
Packit fa4fcc
Packit fa4fcc
eval { Getopt::Long::Configure ("bundling") };
Packit fa4fcc
GetOptions(
Packit fa4fcc
	'b|binary' => \$binary, 'c|check' => \$check,
Packit fa4fcc
	't|text' => \$text, 'a|algorithm=i' => \$alg,
Packit fa4fcc
	's|status' => \$status, 'w|warn' => \$warn,
Packit fa4fcc
	'q|quiet' => \$quiet,
Packit fa4fcc
	'h|help' => \$help, 'v|version' => \$version,
Packit fa4fcc
	'0|01' => \$BITS,
Packit fa4fcc
	'U|UNIVERSAL' => \$UNIVERSAL,
Packit fa4fcc
	'tag' => \$tag,
Packit fa4fcc
	'strict' => \$strict,
Packit fa4fcc
	'ignore-missing' => \$ignore_missing,
Packit fa4fcc
) or usage(1, "");
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Deal with help requests and incorrect uses
Packit fa4fcc
Packit fa4fcc
usage(0)
Packit fa4fcc
	if $help;
Packit fa4fcc
usage(1, "shasum: Ambiguous file mode\n")
Packit fa4fcc
	if scalar(grep {defined $_}
Packit fa4fcc
		($binary, $text, $BITS, $UNIVERSAL)) > 1;
Packit fa4fcc
usage(1, "shasum: --warn option used only when verifying checksums\n")
Packit fa4fcc
	if $warn && !$check;
Packit fa4fcc
usage(1, "shasum: --status option used only when verifying checksums\n")
Packit fa4fcc
	if $status && !$check;
Packit fa4fcc
usage(1, "shasum: --quiet option used only when verifying checksums\n")
Packit fa4fcc
	if $quiet && !$check;
Packit fa4fcc
usage(1, "shasum: --ignore-missing option used only when verifying checksums\n")
Packit fa4fcc
	if $ignore_missing && !$check;
Packit fa4fcc
usage(1, "shasum: --strict option used only when verifying checksums\n")
Packit fa4fcc
	if $strict && !$check;
Packit fa4fcc
usage(1, "shasum: --tag does not support --text mode\n")
Packit fa4fcc
	if $tag && $text;
Packit fa4fcc
usage(1, "shasum: --tag does not support Universal Newlines mode\n")
Packit fa4fcc
	if $tag && $UNIVERSAL;
Packit fa4fcc
usage(1, "shasum: --tag does not support BITS mode\n")
Packit fa4fcc
	if $tag && $BITS;
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Default to SHA-1 unless overridden by command line option
Packit fa4fcc
Packit fa4fcc
my %isAlg = map { $_ => 1 } (1, 224, 256, 384, 512, 512224, 512256);
Packit fa4fcc
$alg = 1 unless defined $alg;
Packit fa4fcc
usage(1, "shasum: Unrecognized algorithm\n") unless $isAlg{$alg};
Packit fa4fcc
Packit fa4fcc
my %Tag = map { $_ => "SHA$_" } (1, 224, 256, 384, 512);
Packit fa4fcc
$Tag{512224} = "SHA512/224";
Packit fa4fcc
$Tag{512256} = "SHA512/256";
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Display version information if requested
Packit fa4fcc
Packit fa4fcc
if ($version) {
Packit fa4fcc
	print "$VERSION\n";
Packit fa4fcc
	exit(0);
Packit fa4fcc
}
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Try to figure out if the OS is DOS-like.  If it is,
Packit fa4fcc
	## default to binary mode when reading files, unless
Packit fa4fcc
	## explicitly overridden by command line "--text" or
Packit fa4fcc
	## "--UNIVERSAL" options.
Packit fa4fcc
Packit fa4fcc
my $isDOSish = ($^O =~ /^(MSWin\d\d|os2|dos|mint|cygwin)$/);
Packit fa4fcc
if ($isDOSish) { $binary = 1 unless $text || $UNIVERSAL }
Packit fa4fcc
Packit fa4fcc
my $modesym = $binary ? '*' : ($UNIVERSAL ? 'U' : ($BITS ? '^' : ' '));
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Read from STDIN (-) if no files listed on command line
Packit fa4fcc
Packit fa4fcc
@ARGV = ("-") unless @ARGV;
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## sumfile($file): computes SHA digest of $file
Packit fa4fcc
Packit fa4fcc
sub sumfile {
Packit fa4fcc
	my $file = shift;
Packit fa4fcc
Packit fa4fcc
	my $mode = $binary ? 'b' : ($UNIVERSAL ? 'U' : ($BITS ? '0' : ''));
Packit fa4fcc
	my $digest = eval { Digest::SHA->new($alg)->addfile($file, $mode) };
Packit fa4fcc
	if ($@) { warn "shasum: $file: $errmsg\n"; return }
Packit fa4fcc
	$digest->hexdigest;
Packit fa4fcc
}
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## %len2alg: maps hex digest length to SHA algorithm
Packit fa4fcc
Packit fa4fcc
my %len2alg = (40 => 1, 56 => 224, 64 => 256, 96 => 384, 128 => 512);
Packit fa4fcc
$len2alg{56} = 512224 if $alg == 512224;
Packit fa4fcc
$len2alg{64} = 512256 if $alg == 512256;
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## unescape: convert backslashed filename to plain filename
Packit fa4fcc
Packit fa4fcc
sub unescape {
Packit fa4fcc
	$_ = shift;
Packit fa4fcc
	s/\\\\/\0/g;
Packit fa4fcc
	s/\\n/\n/g;
Packit fa4fcc
	s/\0/\\/g;
Packit fa4fcc
	return $_;
Packit fa4fcc
}
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## verify: confirm the digest values in a checksum file
Packit fa4fcc
Packit fa4fcc
sub verify {
Packit fa4fcc
	my $checkfile = shift;
Packit fa4fcc
	my ($err, $fmt_errs, $read_errs, $match_errs) = (0, 0, 0, 0);
Packit fa4fcc
	my ($num_fmt_OK, $num_OK) = (0, 0);
Packit fa4fcc
	my ($bslash, $sum, $fname, $rsp, $digest, $isOK);
Packit fa4fcc
Packit fa4fcc
	local *FH;
Packit fa4fcc
	$checkfile eq '-' and open(FH, '< -')
Packit fa4fcc
		and $checkfile = 'standard input'
Packit fa4fcc
	or sysopen(FH, $checkfile, O_RDONLY)
Packit fa4fcc
		or die "shasum: $checkfile: $!\n";
Packit fa4fcc
	while (<FH>) {
Packit fa4fcc
		next if /^#/;
Packit fa4fcc
		if (/^[ \t]*\\?SHA/) {
Packit fa4fcc
			$modesym = '*';
Packit fa4fcc
			($bslash, $alg, $fname, $sum) =
Packit fa4fcc
			/^[ \t]*(\\?)SHA(\S+) \((.+)\) = ([\da-fA-F]+)/;
Packit fa4fcc
			$alg =~ tr{/}{}d if defined $alg;
Packit fa4fcc
		}
Packit fa4fcc
		else {
Packit fa4fcc
			($bslash, $sum, $modesym, $fname) =
Packit fa4fcc
			/^[ \t]*(\\?)([\da-fA-F]+)[ \t]([ *^U])(.+)/;
Packit fa4fcc
			$alg = defined $sum ? $len2alg{length($sum)} : undef;
Packit fa4fcc
		}
Packit fa4fcc
		if (grep { ! defined $_ } ($alg, $sum, $modesym, $fname) or
Packit fa4fcc
			! $isAlg{$alg}) {
Packit fa4fcc
			warn("shasum: $checkfile: $.: improperly " .
Packit fa4fcc
				"formatted SHA checksum line\n") if $warn;
Packit fa4fcc
			$fmt_errs++;
Packit fa4fcc
			$err = 1 if $strict;
Packit fa4fcc
			next;
Packit fa4fcc
		}
Packit fa4fcc
		$num_fmt_OK++;
Packit fa4fcc
		$fname = unescape($fname) if $bslash;
Packit fa4fcc
		next if $ignore_missing && ! -e $fname;
Packit fa4fcc
		$rsp = "$fname: ";
Packit fa4fcc
		($binary, $text, $UNIVERSAL, $BITS) =
Packit fa4fcc
			map { $_ eq $modesym } ('*', ' ', 'U', '^');
Packit fa4fcc
		$isOK = 0;
Packit fa4fcc
		unless ($digest = sumfile($fname)) {
Packit fa4fcc
			$rsp .= "FAILED open or read\n";
Packit fa4fcc
			$err = 1; $read_errs++;
Packit fa4fcc
		}
Packit fa4fcc
		elsif (lc($sum) eq $digest) {
Packit fa4fcc
			$rsp .= "OK\n";
Packit fa4fcc
			$isOK = 1;
Packit fa4fcc
			$num_OK++;
Packit fa4fcc
		}
Packit fa4fcc
		else { $rsp .= "FAILED\n"; $err = 1; $match_errs++ }
Packit fa4fcc
		print $rsp unless ($status || ($quiet && $isOK));
Packit fa4fcc
	}
Packit fa4fcc
	close(FH);
Packit fa4fcc
	if (! $num_fmt_OK) {
Packit fa4fcc
		warn("shasum: $checkfile: no properly formatted " .
Packit fa4fcc
			"SHA checksum lines found\n");
Packit fa4fcc
		$err = 1;
Packit fa4fcc
	}
Packit fa4fcc
	elsif (! $status) {
Packit fa4fcc
		warn("shasum: WARNING: $fmt_errs line" . ($fmt_errs>1?
Packit fa4fcc
		's are':' is') . " improperly formatted\n") if $fmt_errs;
Packit fa4fcc
		warn("shasum: WARNING: $read_errs listed file" .
Packit fa4fcc
		($read_errs>1?'s':'') . " could not be read\n") if $read_errs;
Packit fa4fcc
		warn("shasum: WARNING: $match_errs computed checksum" .
Packit fa4fcc
		($match_errs>1?'s':'') . " did NOT match\n") if $match_errs;
Packit fa4fcc
	}
Packit fa4fcc
	if ($ignore_missing && ! $num_OK && $num_fmt_OK) {
Packit fa4fcc
		warn("shasum: $checkfile: no file was verified\n")
Packit fa4fcc
			unless $status;
Packit fa4fcc
		$err = 1;
Packit fa4fcc
	}
Packit fa4fcc
	return($err == 0);
Packit fa4fcc
}
Packit fa4fcc
Packit fa4fcc
Packit fa4fcc
	## Verify or compute SHA checksums of requested files
Packit fa4fcc
Packit fa4fcc
my($file, $digest);
Packit fa4fcc
my $STATUS = 0;
Packit fa4fcc
for $file (@ARGV) {
Packit fa4fcc
	if ($check) { $STATUS = 1 unless verify($file) }
Packit fa4fcc
	elsif ($digest = sumfile($file)) {
Packit fa4fcc
		if ($file =~ /[\n\\]/) {
Packit fa4fcc
			$file =~ s/\\/\\\\/g; $file =~ s/\n/\\n/g;
Packit fa4fcc
			print "\\";
Packit fa4fcc
		}
Packit fa4fcc
		unless ($tag) { print "$digest $modesym$file\n" }
Packit fa4fcc
		else          { print "$Tag{$alg} ($file) = $digest\n" }
Packit fa4fcc
	}
Packit fa4fcc
	else { $STATUS = 1 }
Packit fa4fcc
}
Packit fa4fcc
exit($STATUS);