#!/usr/bin/perl

# shasum: filter for computing SHA digests
#
# usage: see "sub usage()" directly below
#
# Copyright (C) 2003 Mark Shelor, All Rights Reserved
#
# Version: 2.2
# Wed Nov 12 03:31:35 MST 2003

use strict;
use Getopt::Long;
use File::Temp qw(tempfile);
use Digest::SHA ':all';

sub usage {
	my($err) = @_;

	my $stream = $err ? *STDERR : *STDOUT;
	print $stream <<"EOU";
Usage: shasum [options] [file] ...
   or: shasum [options] --check [file]
Print or check SHA checksums
With no file, or when file is -, read standard input

	-b, --binary	read files in binary mode
	-c, --check	check sums against given list
	-t, --text	read files in text mode (default)

	-a, --alg 	1 (default), 256, 384, 512
	-i, --input 	bytes (default), bits
	-o, --output	hex (default), base64, raw

	-d, --dump	dumps intermediate SHA state to stdout
	-l, --load	loads intermediate SHA state from file or -

	The following two options apply only when using --check
	-s, --status	print nothing; just return program status code
	-w, --warn	warn about improperly formatted checksum lines

	-h, --help	display help and exit
	-v, --version	output version information and exit
EOU
	exit($err);
}

my $VERSION = "2.2";

my ($binary, $check, $dump, $load, $status, $warn, $help, $version);
my $text = 1;
my $alg = 1;
my $input = "bytes";
my $output = "hex";

GetOptions(
	'binary' => \$binary, 'check' => \$check,
	'text' => \$text, 'alg=i' => \$alg,
	'input=s' => \$input, 'output=s' => \$output,
	'dump' => \$dump, 'load=s' => \$load,
	'status' => \$status, 'warn' => \$warn,
	'help' => \$help, 'version' => \$version
) or usage(1);

usage(0) if $help;
if ($version) {
	print "$VERSION\n";
	exit(0);
}

usage(1) if $warn and not $check;
usage(1) if $status and not $check;

grep { $_ eq $input } ("bytes", "bits") or usage(1);
grep { $_ eq $output } ("hex", "base64", "raw") or usage(1);
grep { $_ == $alg } (1, 256, 384, 512) or usage(1);

sub predigest {
	my($file, $state) = @_;
	my($fh, $buf, $len);

	unless (open($fh, "<$file")) {
		print STDERR "shasum: $file: No such file or directory\n";
		return;
	}
	binmode($fh) if $binary;
	$state = shaopen($alg) if not defined($state);
	while (read($fh, $buf, 1<<12)) {
		if ($input eq "bits") {
			$len = length($buf);
			$buf = pack("B*", $buf);
			shawrite($buf, $len, $state);
		}
		else {
			shawrite($buf, $state);
		}
	}
	close($fh);
	return($state);
}

sub digest {
	my($file, $state) = @_;
	my($out);

	return unless $state = predigest($file, $state);
	shafinish($state);
	$out = $output eq "base64" ? shabase64($state) : shahex($state);
	shaclose($state);
	$out = pack("H*", $out) if ($output eq "raw");
	return($out);
}

sub putdigest {
	my($file, $state) = @_;
	my($digest);

	return unless $digest = digest($file, $state);
	print $digest;
	print " ", $binary ? "\*" : " ", "$file\n" unless ($output eq "raw");
}

@ARGV = ("-") unless @ARGV;

if ($dump) {
	my $file = shift(@ARGV);
	my ($state);

	exit(1) unless $state = predigest($file);
	shadump($state);
	shaclose($state);
	exit(0);
}

if ($load) {
	my $file = shift(@ARGV);
	my ($fh, $state, $buf, $len, $out);

	$load = "" if $load eq "-";
	unless ($state = shaload($load)) {
		$load = "-" if $load eq "";
		print STDERR "shasum: $load: Error opening load file\n";
		exit(1);
	}
	exit(1) unless putdigest($file, $state);
	exit(0);
}

if ($check) {
	my $file = shift(@ARGV);
	my $err = 0;
	my $linenum = 0;
	my ($rsp, $msg, $fh, @f);

	unless (open($fh, "<$file")) {
		print STDERR "shasum: $file: No such file or directory\n";
		exit(1);
	}
	while (<$fh>) {
		@f = split; $linenum++;
		$binary = substr($f[1], 0, 1) eq '*';
		$f[1] = substr($f[1], 1) if substr($f[1], 0, 1) eq '*';
		$rsp = "$f[1]: ";
		unless (grep {$_==length($f[0])} (27,40,43,64,86,96,128)) {
			$msg = "shasum: $file: $linenum: ";
			$msg .= "improperly formatted SHA checksum line\n";
			print STDERR $msg if $warn and not $status;
			next;
		}
		($output, $alg) = ("hex", 1) if length($f[0]) == 40;
		($output, $alg) = ("hex", 256) if length($f[0]) == 64;
		($output, $alg) = ("hex", 384) if length($f[0]) == 96;
		($output, $alg) = ("hex", 512) if length($f[0]) == 128;
		($output, $alg) = ("base64", 1) if length($f[0]) == 27;
		($output, $alg) = ("base64", 256) if length($f[0]) == 43;
		($output, $alg) = ("base64", 512) if length($f[0]) == 86;
		if ($f[0] eq digest($f[1])) {
			$rsp .= "OK\n";
		}
		else {
			if ($output eq "hex" && $alg == 256) {
				($output, $alg) = ("base64", 384);
				if ($f[0] eq digest($f[1])) {
					$rsp .= "OK\n";
				}
				else {
					$err = 1;
					$rsp .= "FAILED\n";
				}
			}
			else {
				$err = 1;
				$rsp .= "FAILED\n";
			}
		}
		print $rsp unless $status;
	}
	close($fh);
	exit($err);
}

for (@ARGV) {
	if (-d $_) {
		print STDERR "shasum: $_: Is a directory\n";
		next;
	}
	putdigest($_);
}
