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