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