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