1#!perl
2
3	## shasum: filter for computing SHA digests (ref. sha1sum/md5sum)
4	##
5	## Copyright (C) 2003-2016 Mark Shelor, All Rights Reserved
6	##
7	## Version: 5.96
8	## Wed Jul 27 20:04:34 MST 2016
9
10	## shasum SYNOPSIS adapted from GNU Coreutils sha1sum. Add
11	## "-a" option for algorithm selection,
12	## "-U" option for Universal Newlines support,
13	## "-0" option for reading bit strings, and
14	## "-p" option for portable digests (to be deprecated).
15
16BEGIN { pop @INC if $INC[-1] eq '.' }
17
18use strict;
19use warnings;
20use Fcntl;
21use Getopt::Long;
22
23my $POD = <<'END_OF_POD';
24
25=head1 NAME
26
27shasum - Print or Check SHA Checksums
28
29=head1 SYNOPSIS
30
31 Usage: shasum [OPTION]... [FILE]...
32 Print or check SHA checksums.
33 With no FILE, or when FILE is -, read standard input.
34
35   -a, --algorithm   1 (default), 224, 256, 384, 512, 512224, 512256
36   -b, --binary      read in binary mode
37   -c, --check       read SHA sums from the FILEs and check them
38   -t, --text        read in text mode (default)
39   -U, --UNIVERSAL   read in Universal Newlines mode
40                         produces same digest on Windows/Unix/Mac
41   -0, --01          read in BITS mode
42                         ASCII '0' interpreted as 0-bit,
43                         ASCII '1' interpreted as 1-bit,
44                         all other characters ignored
45   -p, --portable    read in portable mode (to be deprecated)
46
47 The following two options are useful only when verifying checksums:
48   -s, --status      don't output anything, status code shows success
49   -w, --warn        warn about improperly formatted checksum lines
50
51   -h, --help        display this help and exit
52   -v, --version     output version information and exit
53
54 When verifying SHA-512/224 or SHA-512/256 checksums, indicate the
55 algorithm explicitly using the -a option, e.g.
56
57   shasum -a 512224 -c checksumfile
58
59 The sums are computed as described in FIPS PUB 180-4.  When checking,
60 the input should be a former output of this program.  The default
61 mode is to print a line with checksum, a character indicating type
62 (`*' for binary, ` ' for text, `U' for UNIVERSAL, `^' for BITS, `?'
63 for portable), and name for each FILE.
64
65 Report shasum bugs to mshelor@cpan.org
66
67=head1 DESCRIPTION
68
69Running I<shasum> is often the quickest way to compute SHA message
70digests.  The user simply feeds data to the script through files or
71standard input, and then collects the results from standard output.
72
73The following command shows how to compute digests for typical inputs
74such as the NIST test vector "abc":
75
76	perl -e "print qq(abc)" | shasum
77
78Or, if you want to use SHA-256 instead of the default SHA-1, simply say:
79
80	perl -e "print qq(abc)" | shasum -a 256
81
82Since I<shasum> mimics the behavior of the combined GNU I<sha1sum>,
83I<sha224sum>, I<sha256sum>, I<sha384sum>, and I<sha512sum> programs,
84you can install this script as a convenient drop-in replacement.
85
86Unlike the GNU programs, I<shasum> encompasses the full SHA standard by
87allowing partial-byte inputs.  This is accomplished through the BITS
88option (I<-0>).  The following example computes the SHA-224 digest of
89the 7-bit message I<0001100>:
90
91	perl -e "print qq(0001100)" | shasum -0 -a 224
92
93=head1 AUTHOR
94
95Copyright (c) 2003-2016 Mark Shelor <mshelor@cpan.org>.
96
97=head1 SEE ALSO
98
99I<shasum> is implemented using the Perl module L<Digest::SHA> or
100L<Digest::SHA::PurePerl>.
101
102=cut
103
104END_OF_POD
105
106my $VERSION = "5.96";
107
108sub usage {
109	my($err, $msg) = @_;
110
111	$msg = "" unless defined $msg;
112	if ($err) {
113		warn($msg . "Type shasum -h for help\n");
114		exit($err);
115	}
116	my($USAGE) = $POD =~ /SYNOPSIS(.+?)^=/sm;
117	$USAGE =~ s/^\s*//;
118	$USAGE =~ s/\s*$//;
119	$USAGE =~ s/^ //gm;
120	print $USAGE, "\n";
121	exit($err);
122}
123
124
125	## Sync stdout and stderr by forcing a flush after every write
126
127select((select(STDOUT), $| = 1)[0]);
128select((select(STDERR), $| = 1)[0]);
129
130
131	## Collect options from command line
132
133my ($alg, $binary, $check, $text, $status, $warn, $help, $version);
134my ($portable, $BITS, $reverse, $UNIVERSAL, $versions);
135
136eval { Getopt::Long::Configure ("bundling") };
137GetOptions(
138	'b|binary' => \$binary, 'c|check' => \$check,
139	't|text' => \$text, 'a|algorithm=i' => \$alg,
140	's|status' => \$status, 'w|warn' => \$warn,
141	'h|help' => \$help, 'v|version' => \$version,
142	'p|portable' => \$portable,
143	'0|01' => \$BITS,
144	'R|REVERSE' => \$reverse,
145	'U|UNIVERSAL' => \$UNIVERSAL,
146	'V|VERSIONS' => \$versions,
147) or usage(1, "");
148
149
150	## Deal with help requests and incorrect uses
151
152usage(0)
153	if $help;
154usage(1, "shasum: Ambiguous file mode\n")
155	if scalar(grep {defined $_}
156		($binary, $portable, $text, $BITS, $UNIVERSAL)) > 1;
157usage(1, "shasum: --warn option used only when verifying checksums\n")
158	if $warn && !$check;
159usage(1, "shasum: --status option used only when verifying checksums\n")
160	if $status && !$check;
161
162
163	## Try to use Digest::SHA.  If not installed, use the slower
164	## but functionally equivalent Digest::SHA::PurePerl instead.
165
166	## If option -R is invoked, reverse the module preference,
167	## i.e. try Digest::SHA::PurePerl first, then Digest::SHA.
168
169my @MODS = qw(Digest::SHA Digest::SHA::PurePerl);
170@MODS[0, 1] = @MODS[1, 0] if $reverse;
171
172my $module;
173for (@MODS) {
174	my $mod = $_;
175	if (eval "require $mod") {
176		$module = $mod;
177		last;
178	}
179}
180die "shasum: Unable to find " . join(" or ", @MODS) . "\n"
181	unless defined $module;
182
183
184	## Default to SHA-1 unless overridden by command line option
185
186$alg = 1 unless defined $alg;
187grep { $_ == $alg } (1, 224, 256, 384, 512, 512224, 512256)
188	or usage(1, "shasum: Unrecognized algorithm\n");
189
190
191	## Display version information if requested
192
193if ($version) {
194	print "$VERSION\n";
195	exit(0);
196}
197
198if ($versions) {
199	print "shasum $VERSION\n";
200	print "$module ", eval "\$${module}::VERSION", "\n";
201	print "perl ", defined $^V ? sprintf("%vd", $^V) : $], "\n";
202	exit(0);
203}
204
205
206	## Try to figure out if the OS is DOS-like.  If it is,
207	## default to binary mode when reading files, unless
208	## explicitly overridden by command line "--text" or
209	## "--UNIVERSAL" or "--portable" options.
210
211my $isDOSish = ($^O =~ /^(MSWin\d\d|os2|dos|mint|cygwin)$/);
212if ($isDOSish) { $binary = 1 unless $text || $UNIVERSAL || $portable }
213
214my $modesym = $binary ? '*' : ($UNIVERSAL ? 'U' :
215		($BITS ? '^' : ($portable ? '?' : ' ')));
216
217
218	## Read from STDIN (-) if no files listed on command line
219
220@ARGV = ("-") unless @ARGV;
221
222
223	## sumfile($file): computes SHA digest of $file
224
225sub sumfile {
226	my $file = shift;
227
228	my $mode = $binary ? 'b' : ($UNIVERSAL ? 'U' :
229			($BITS ? '0' : ($portable ? 'p' : '')));
230	my $digest = eval { $module->new($alg)->addfile($file, $mode) };
231	if ($@) { warn "shasum: $file: $!\n"; return }
232	$digest->hexdigest;
233}
234
235
236	## %len2alg: maps hex digest length to SHA algorithm
237
238my %len2alg = (40 => 1, 56 => 224, 64 => 256, 96 => 384, 128 => 512);
239$len2alg{56} = 512224 if $alg == 512224;
240$len2alg{64} = 512256 if $alg == 512256;
241
242
243	## unescape: convert backslashed filename to plain filename
244
245sub unescape {
246	$_ = shift;
247	s/\\\\/\0/g;
248	s/\\n/\n/g;
249	return if /\\/;
250	s/\0/\\/g;
251	return $_;
252}
253
254
255	## verify: confirm the digest values in a checksum file
256
257sub verify {
258	my $checkfile = shift;
259	my ($err, $fmt_errs, $read_errs, $match_errs) = (0, 0, 0, 0);
260	my ($num_lines, $num_files) = (0, 0);
261	my ($bslash, $sum, $fname, $rsp, $digest);
262
263	local *FH;
264	$checkfile eq '-' and open(FH, '< -')
265		and $checkfile = 'standard input'
266	or sysopen(FH, $checkfile, O_RDONLY)
267		or die "shasum: $checkfile: $!\n";
268	while (<FH>) {
269		next if /^#/; s/\n$//; s/^[ \t]+//; $num_lines++;
270		$bslash = s/^\\//;
271		($sum, $modesym, $fname) =
272			/^([\da-fA-F]+)[ \t]([ *?^U])([^\0]*)/;
273		$alg = defined $sum ? $len2alg{length($sum)} : undef;
274		$fname = unescape($fname) if defined $fname && $bslash;
275		if (grep { ! defined $_ } ($alg, $sum, $modesym, $fname)) {
276			$alg = 1 unless defined $alg;
277			warn("shasum: $checkfile: $.: improperly " .
278				"formatted SHA$alg checksum line\n") if $warn;
279			$fmt_errs++;
280			next;
281		}
282		$fname =~ s/\r$// unless -e $fname;
283		$rsp = "$fname: "; $num_files++;
284		($binary, $text, $UNIVERSAL, $BITS, $portable) =
285			map { $_ eq $modesym } ('*', ' ', 'U', '^', 'p');
286		unless ($digest = sumfile($fname)) {
287			$rsp .= "FAILED open or read\n";
288			$err = 1; $read_errs++;
289		}
290		else {
291			if (lc($sum) eq $digest) { $rsp .= "OK\n" }
292			else { $rsp .= "FAILED\n"; $err = 1; $match_errs++ }
293		}
294		print $rsp unless $status;
295	}
296	close(FH);
297	unless ($num_files) {
298		$alg = 1 unless defined $alg;
299		warn("shasum: $checkfile: no properly formatted " .
300			"SHA$alg checksum lines found\n");
301		$err = 1;
302	}
303	elsif (! $status) {
304		warn("shasum: WARNING: $fmt_errs line" . ($fmt_errs>1?
305		's are':' is') . " improperly formatted\n") if $fmt_errs;
306		warn("shasum: WARNING: $read_errs listed file" .
307		($read_errs>1?'s':'') . " could not be read\n") if $read_errs;
308		warn("shasum: WARNING: $match_errs computed checksum" .
309		($match_errs>1?'s':'') . " did NOT match\n") if $match_errs;
310	}
311	return($err == 0);
312}
313
314
315	## Verify or compute SHA checksums of requested files
316
317my($file, $digest);
318
319my $STATUS = 0;
320for $file (@ARGV) {
321	if ($check) { $STATUS = 1 unless verify($file) }
322	elsif ($digest = sumfile($file)) {
323		if ($file =~ /[\n\\]/) {
324			$file =~ s/\\/\\\\/g; $file =~ s/\n/\\n/g;
325			$digest = "\\$digest";
326		}
327		print "$digest $modesym", "$file\n";
328	}
329	else { $STATUS = 1 }
330}
331exit($STATUS)
332