blob: 4acf97c8b7aaffaea378edfe96111994d079da1f [file] [log] [blame]
Adam Langleycca4d592015-01-12 12:01:23 -08001/* Copyright (c) 2014, Google Inc.
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15#include <openssl/base.h>
16
Adam Langleycca4d592015-01-12 12:01:23 -080017#include <memory>
18#include <string>
19#include <vector>
20
21#include <errno.h>
22#include <fcntl.h>
23#include <limits.h>
24#include <stdio.h>
Adam Langley2b2d66d2015-01-30 17:08:37 -080025#include <string.h>
Adam Langleycca4d592015-01-12 12:01:23 -080026#include <sys/stat.h>
27#include <sys/types.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080028
29#if !defined(OPENSSL_WINDOWS)
Adam Langleycca4d592015-01-12 12:01:23 -080030#include <unistd.h>
Brian Smithafdaeee2015-01-26 19:54:32 -080031#if !defined(O_BINARY)
32#define O_BINARY 0
33#endif
34#else
Brian Smithefed2212015-01-28 16:20:02 -080035#pragma warning(push, 3)
Brian Smithafdaeee2015-01-26 19:54:32 -080036#include <windows.h>
Brian Smithefed2212015-01-28 16:20:02 -080037#pragma warning(pop)
Brian Smithafdaeee2015-01-26 19:54:32 -080038#include <io.h>
39#define PATH_MAX MAX_PATH
Brian Smith33970e62015-01-27 22:32:08 -080040typedef int ssize_t;
Brian Smithafdaeee2015-01-26 19:54:32 -080041#endif
Adam Langleycca4d592015-01-12 12:01:23 -080042
43#include <openssl/digest.h>
44
Adam Langley2b2d66d2015-01-30 17:08:37 -080045
Adam Langleycca4d592015-01-12 12:01:23 -080046struct close_delete {
47 void operator()(int *fd) {
48 close(*fd);
49 }
50};
51
52template<typename T, typename R, R (*func) (T*)>
53struct func_delete {
54 void operator()(T* obj) {
55 func(obj);
56 }
57};
58
59// Source is an awkward expression of a union type in C++: Stdin | File filename.
60struct Source {
61 enum Type {
62 STDIN,
63 };
64
65 Source() : is_stdin_(false) {}
66 Source(Type) : is_stdin_(true) {}
67 Source(const std::string &name) : is_stdin_(false), filename_(name) {}
68
69 bool is_stdin() const { return is_stdin_; }
70 const std::string &filename() const { return filename_; }
71
72 private:
73 bool is_stdin_;
74 std::string filename_;
75};
76
77static const char kStdinName[] = "standard input";
78
79// OpenFile opens the regular file named |filename| and sets |*out_fd| to be a
80// file descriptor to it. Returns true on sucess or prints an error to stderr
81// and returns false on error.
82static bool OpenFile(int *out_fd, const std::string &filename) {
83 *out_fd = -1;
84
Brian Smithafdaeee2015-01-26 19:54:32 -080085 int fd = open(filename.c_str(), O_RDONLY | O_BINARY);
Adam Langleycca4d592015-01-12 12:01:23 -080086 if (fd < 0) {
87 fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(),
88 strerror(errno));
89 return false;
90 }
91
Brian Smithafdaeee2015-01-26 19:54:32 -080092#if !defined(OPENSSL_WINDOWS)
Adam Langleycca4d592015-01-12 12:01:23 -080093 struct stat st;
94 if (fstat(fd, &st)) {
95 fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(),
96 strerror(errno));
97 goto err;
98 }
99
100 if (!S_ISREG(st.st_mode)) {
101 fprintf(stderr, "%s: not a regular file\n", filename.c_str());
102 goto err;
103 }
Brian Smithafdaeee2015-01-26 19:54:32 -0800104#endif
Adam Langleycca4d592015-01-12 12:01:23 -0800105
106 *out_fd = fd;
107 return true;
108
Brian Smithefed2212015-01-28 16:20:02 -0800109#if !defined(OPENSSL_WINDOWS)
Adam Langleycca4d592015-01-12 12:01:23 -0800110err:
111 close(fd);
112 return false;
Brian Smithefed2212015-01-28 16:20:02 -0800113#endif
Adam Langleycca4d592015-01-12 12:01:23 -0800114}
115
116// SumFile hashes the contents of |source| with |md| and sets |*out_hex| to the
117// hex-encoded result.
118//
119// It returns true on success or prints an error to stderr and returns false on
120// error.
121static bool SumFile(std::string *out_hex, const EVP_MD *md,
122 const Source &source) {
123 std::unique_ptr<int, close_delete> scoped_fd;
124 int fd;
125
126 if (source.is_stdin()) {
127 fd = 0;
128 } else {
129 if (!OpenFile(&fd, source.filename())) {
130 return false;
131 }
132 scoped_fd.reset(&fd);
133 }
134
135 static const size_t kBufSize = 8192;
136 std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
137
138 EVP_MD_CTX ctx;
139 EVP_MD_CTX_init(&ctx);
140 std::unique_ptr<EVP_MD_CTX, func_delete<EVP_MD_CTX, int, EVP_MD_CTX_cleanup>>
141 scoped_ctx(&ctx);
142
143 if (!EVP_DigestInit_ex(&ctx, md, NULL)) {
144 fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n");
145 return false;
146 }
147
148 for (;;) {
149 ssize_t n;
150
151 do {
152 n = read(fd, buf.get(), kBufSize);
153 } while (n == -1 && errno == EINTR);
154
155 if (n == 0) {
156 break;
157 } else if (n < 0) {
158 fprintf(stderr, "Failed to read from %s: %s\n",
159 source.is_stdin() ? kStdinName : source.filename().c_str(),
160 strerror(errno));
161 return false;
162 }
163
164 if (!EVP_DigestUpdate(&ctx, buf.get(), n)) {
165 fprintf(stderr, "Failed to update hash.\n");
166 return false;
167 }
168 }
169
170 uint8_t digest[EVP_MAX_MD_SIZE];
171 unsigned digest_len;
172 if (!EVP_DigestFinal_ex(&ctx, digest, &digest_len)) {
173 fprintf(stderr, "Failed to finish hash.\n");
174 return false;
175 }
176
177 char hex_digest[EVP_MAX_MD_SIZE * 2];
178 static const char kHextable[] = "0123456789abcdef";
179 for (unsigned i = 0; i < digest_len; i++) {
180 const uint8_t b = digest[i];
181 hex_digest[i * 2] = kHextable[b >> 4];
182 hex_digest[i * 2 + 1] = kHextable[b & 0xf];
183 }
184 *out_hex = std::string(hex_digest, digest_len * 2);
185
186 return true;
187}
188
189// PrintFileSum hashes |source| with |md| and prints a line to stdout in the
190// format of the coreutils *sum utilities. It returns true on success or prints
191// an error to stderr and returns false on error.
192static bool PrintFileSum(const EVP_MD *md, const Source &source) {
193 std::string hex_digest;
194 if (!SumFile(&hex_digest, md, source)) {
195 return false;
196 }
197
Brian Smithafdaeee2015-01-26 19:54:32 -0800198 // TODO: When given "--binary" or "-b", we should print " *" instead of " "
199 // between the digest and the filename.
200 //
201 // MSYS and Cygwin md5sum default to binary mode by default, whereas other
202 // platforms' tools default to text mode by default. We default to text mode
203 // by default and consider text mode equivalent to binary mode (i.e. we
204 // always use Unix semantics, even on Windows), which means that our default
205 // output will differ from the MSYS and Cygwin tools' default output.
Adam Langleycca4d592015-01-12 12:01:23 -0800206 printf("%s %s\n", hex_digest.c_str(),
207 source.is_stdin() ? "-" : source.filename().c_str());
208 return true;
209}
210
211// CheckModeArguments contains arguments for the check mode. See the
212// sha256sum(1) man page for details.
213struct CheckModeArguments {
214 bool quiet = false;
215 bool status = false;
216 bool warn = false;
217 bool strict = false;
218};
219
220// Check reads lines from |source| where each line is in the format of the
221// coreutils *sum utilities. It attempts to verify each hash by reading the
222// file named in the line.
223//
224// It returns true if all files were verified and, if |args.strict|, no input
225// lines had formatting errors. Otherwise it prints errors to stderr and
226// returns false.
227static bool Check(const CheckModeArguments &args, const EVP_MD *md,
228 const Source &source) {
229 std::unique_ptr<FILE, func_delete<FILE, int, fclose>> scoped_file;
230 FILE *file;
231
232 if (source.is_stdin()) {
233 file = stdin;
234 } else {
235 int fd;
236 if (!OpenFile(&fd, source.filename())) {
237 return false;
238 }
239
Brian Smithafdaeee2015-01-26 19:54:32 -0800240 file = fdopen(fd, "rb");
Adam Langleycca4d592015-01-12 12:01:23 -0800241 if (!file) {
242 perror("fdopen");
243 close(fd);
244 return false;
245 }
246
247 scoped_file = std::unique_ptr<FILE, func_delete<FILE, int, fclose>>(file);
248 }
249
250 const size_t hex_size = EVP_MD_size(md) * 2;
251 char line[EVP_MAX_MD_SIZE * 2 + 2 /* spaces */ + PATH_MAX + 1 /* newline */ +
252 1 /* NUL */];
253 unsigned bad_lines = 0;
254 unsigned parsed_lines = 0;
255 unsigned error_lines = 0;
256 unsigned bad_hash_lines = 0;
257 unsigned line_no = 0;
258 bool ok = true;
259 bool draining_overlong_line = false;
260
261 for (;;) {
262 line_no++;
263
264 if (fgets(line, sizeof(line), file) == nullptr) {
265 if (feof(file)) {
266 break;
267 }
268 fprintf(stderr, "Error reading from input.\n");
269 return false;
270 }
271
272 size_t len = strlen(line);
273
274 if (draining_overlong_line) {
275 if (line[len - 1] == '\n') {
276 draining_overlong_line = false;
277 }
278 continue;
279 }
280
281 const bool overlong = line[len - 1] != '\n' && !feof(file);
282
283 if (len < hex_size + 2 /* spaces */ + 1 /* filename */ ||
284 line[hex_size] != ' ' ||
285 line[hex_size + 1] != ' ' ||
286 overlong) {
287 bad_lines++;
288 if (args.warn) {
289 fprintf(stderr, "%s: %u: improperly formatted line\n",
290 source.is_stdin() ? kStdinName : source.filename().c_str(), line_no);
291 }
292 if (args.strict) {
293 ok = false;
294 }
295 if (overlong) {
296 draining_overlong_line = true;
297 }
298 continue;
299 }
300
301 if (line[len - 1] == '\n') {
302 line[len - 1] = 0;
303 len--;
304 }
305
306 parsed_lines++;
307
308 // coreutils does not attempt to restrict relative or absolute paths in the
309 // input so nor does this code.
310 std::string calculated_hex_digest;
311 const std::string target_filename(&line[hex_size + 2]);
312 Source target_source;
313 if (target_filename == "-") {
314 // coreutils reads from stdin if the filename is "-".
315 target_source = Source(Source::STDIN);
316 } else {
317 target_source = Source(target_filename);
318 }
319
320 if (!SumFile(&calculated_hex_digest, md, target_source)) {
321 error_lines++;
322 ok = false;
323 continue;
324 }
325
326 if (calculated_hex_digest != std::string(line, hex_size)) {
327 bad_hash_lines++;
328 if (!args.status) {
329 printf("%s: FAILED\n", target_filename.c_str());
330 }
331 ok = false;
332 continue;
333 }
334
335 if (!args.quiet) {
336 printf("%s: OK\n", target_filename.c_str());
337 }
338 }
339
340 if (!args.status) {
341 if (bad_lines > 0 && parsed_lines > 0) {
342 fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines,
343 bad_lines == 1 ? " is" : "s are");
344 }
345 if (error_lines > 0) {
346 fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n",
347 error_lines);
348 }
349 }
350
351 if (parsed_lines == 0) {
352 fprintf(stderr, "%s: no properly formatted checksum lines found.\n",
353 source.is_stdin() ? kStdinName : source.filename().c_str());
354 ok = false;
355 }
356
357 return ok;
358}
359
360// DigestSum acts like the coreutils *sum utilites, with the given hash
361// function.
362static bool DigestSum(const EVP_MD *md,
363 const std::vector<std::string> &args) {
364 bool check_mode = false;
365 CheckModeArguments check_args;
366 bool check_mode_args_given = false;
367 std::vector<Source> sources;
368
369 auto it = args.begin();
370 while (it != args.end()) {
371 const std::string &arg = *it;
372 if (!arg.empty() && arg[0] != '-') {
373 break;
374 }
375
376 it++;
377
378 if (arg == "--") {
379 break;
380 }
381
Adam Langleybed8f732015-01-26 16:34:37 -0800382 if (arg == "-") {
Adam Langleycca4d592015-01-12 12:01:23 -0800383 // "-" ends the argument list and indicates that stdin should be used.
384 sources.push_back(Source(Source::STDIN));
385 break;
386 }
387
388 if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') {
389 for (size_t i = 1; i < arg.size(); i++) {
390 switch (arg[i]) {
391 case 'b':
392 case 't':
Brian Smithafdaeee2015-01-26 19:54:32 -0800393 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800394 break;
395 case 'c':
396 check_mode = true;
397 break;
398 case 'w':
399 check_mode_args_given = true;
400 check_args.warn = true;
401 break;
402 default:
403 fprintf(stderr, "Unknown option '%c'.\n", arg[i]);
404 return false;
405 }
406 }
407 } else if (arg == "--binary" || arg == "--text") {
Brian Smithafdaeee2015-01-26 19:54:32 -0800408 // Binary/text mode – irrelevent, even on Windows.
Adam Langleycca4d592015-01-12 12:01:23 -0800409 } else if (arg == "--check") {
410 check_mode = true;
411 } else if (arg == "--quiet") {
412 check_mode_args_given = true;
413 check_args.quiet = true;
414 } else if (arg == "--status") {
415 check_mode_args_given = true;
416 check_args.status = true;
417 } else if (arg == "--warn") {
418 check_mode_args_given = true;
419 check_args.warn = true;
420 } else if (arg == "--strict") {
421 check_mode_args_given = true;
422 check_args.strict = true;
423 } else {
424 fprintf(stderr, "Unknown option '%s'.\n", arg.c_str());
425 return false;
426 }
427 }
428
429 if (check_mode_args_given && !check_mode) {
430 fprintf(
431 stderr,
432 "Check mode arguments are only meaningful when verifying checksums.\n");
433 return false;
434 }
435
436 for (; it != args.end(); it++) {
437 sources.push_back(Source(*it));
438 }
439
440 if (sources.empty()) {
441 sources.push_back(Source(Source::STDIN));
442 }
443
444 bool ok = true;
445
446 if (check_mode) {
447 for (auto &source : sources) {
448 ok &= Check(check_args, md, source);
449 }
450 } else {
451 for (auto &source : sources) {
452 ok &= PrintFileSum(md, source);
453 }
454 }
455
456 return ok;
457}
458
459bool MD5Sum(const std::vector<std::string> &args) {
460 return DigestSum(EVP_md5(), args);
461}
462
463bool SHA1Sum(const std::vector<std::string> &args) {
464 return DigestSum(EVP_sha1(), args);
465}
466
467bool SHA224Sum(const std::vector<std::string> &args) {
468 return DigestSum(EVP_sha224(), args);
469}
470
471bool SHA256Sum(const std::vector<std::string> &args) {
472 return DigestSum(EVP_sha256(), args);
473}
474
475bool SHA384Sum(const std::vector<std::string> &args) {
476 return DigestSum(EVP_sha384(), args);
477}
478
479bool SHA512Sum(const std::vector<std::string> &args) {
480 return DigestSum(EVP_sha512(), args);
481}