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