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