| /* Copyright (c) 2014, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| #include <openssl/base.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #if !defined(OPENSSL_WINDOWS) |
| #include <string.h> |
| #include <unistd.h> |
| #if !defined(O_BINARY) |
| #define O_BINARY 0 |
| #endif |
| #else |
| OPENSSL_MSVC_PRAGMA(warning(push, 3)) |
| #include <windows.h> |
| OPENSSL_MSVC_PRAGMA(warning(pop)) |
| #include <io.h> |
| #define PATH_MAX MAX_PATH |
| typedef int ssize_t; |
| #endif |
| |
| #include <openssl/digest.h> |
| |
| #include "internal.h" |
| |
| |
| struct close_delete { |
| void operator()(int *fd) { |
| BORINGSSL_CLOSE(*fd); |
| } |
| }; |
| |
| template<typename T, typename R, R (*func) (T*)> |
| struct func_delete { |
| void operator()(T* obj) { |
| func(obj); |
| } |
| }; |
| |
| // Source is an awkward expression of a union type in C++: Stdin | File filename. |
| struct Source { |
| enum Type { |
| STDIN, |
| }; |
| |
| Source() : is_stdin_(false) {} |
| Source(Type) : is_stdin_(true) {} |
| Source(const std::string &name) : is_stdin_(false), filename_(name) {} |
| |
| bool is_stdin() const { return is_stdin_; } |
| const std::string &filename() const { return filename_; } |
| |
| private: |
| bool is_stdin_; |
| std::string filename_; |
| }; |
| |
| static const char kStdinName[] = "standard input"; |
| |
| // OpenFile opens the regular file named |filename| and sets |*out_fd| to be a |
| // file descriptor to it. Returns true on sucess or prints an error to stderr |
| // and returns false on error. |
| static bool OpenFile(int *out_fd, const std::string &filename) { |
| *out_fd = -1; |
| |
| int fd = BORINGSSL_OPEN(filename.c_str(), O_RDONLY | O_BINARY); |
| if (fd < 0) { |
| fprintf(stderr, "Failed to open input file '%s': %s\n", filename.c_str(), |
| strerror(errno)); |
| return false; |
| } |
| std::unique_ptr<int, close_delete> scoped_fd(&fd); |
| |
| #if !defined(OPENSSL_WINDOWS) |
| struct stat st; |
| if (fstat(fd, &st)) { |
| fprintf(stderr, "Failed to stat input file '%s': %s\n", filename.c_str(), |
| strerror(errno)); |
| return false; |
| } |
| |
| if (!S_ISREG(st.st_mode)) { |
| fprintf(stderr, "%s: not a regular file\n", filename.c_str()); |
| return false; |
| } |
| #endif |
| |
| *out_fd = fd; |
| scoped_fd.release(); |
| return true; |
| } |
| |
| // SumFile hashes the contents of |source| with |md| and sets |*out_hex| to the |
| // hex-encoded result. |
| // |
| // It returns true on success or prints an error to stderr and returns false on |
| // error. |
| static bool SumFile(std::string *out_hex, const EVP_MD *md, |
| const Source &source) { |
| std::unique_ptr<int, close_delete> scoped_fd; |
| int fd; |
| |
| if (source.is_stdin()) { |
| fd = 0; |
| } else { |
| if (!OpenFile(&fd, source.filename())) { |
| return false; |
| } |
| scoped_fd.reset(&fd); |
| } |
| |
| static const size_t kBufSize = 8192; |
| std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]); |
| |
| EVP_MD_CTX ctx; |
| EVP_MD_CTX_init(&ctx); |
| std::unique_ptr<EVP_MD_CTX, func_delete<EVP_MD_CTX, int, EVP_MD_CTX_cleanup>> |
| scoped_ctx(&ctx); |
| |
| if (!EVP_DigestInit_ex(&ctx, md, NULL)) { |
| fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n"); |
| return false; |
| } |
| |
| for (;;) { |
| ssize_t n; |
| |
| do { |
| n = BORINGSSL_READ(fd, buf.get(), kBufSize); |
| } while (n == -1 && errno == EINTR); |
| |
| if (n == 0) { |
| break; |
| } else if (n < 0) { |
| fprintf(stderr, "Failed to read from %s: %s\n", |
| source.is_stdin() ? kStdinName : source.filename().c_str(), |
| strerror(errno)); |
| return false; |
| } |
| |
| if (!EVP_DigestUpdate(&ctx, buf.get(), n)) { |
| fprintf(stderr, "Failed to update hash.\n"); |
| return false; |
| } |
| } |
| |
| uint8_t digest[EVP_MAX_MD_SIZE]; |
| unsigned digest_len; |
| if (!EVP_DigestFinal_ex(&ctx, digest, &digest_len)) { |
| fprintf(stderr, "Failed to finish hash.\n"); |
| return false; |
| } |
| |
| char hex_digest[EVP_MAX_MD_SIZE * 2]; |
| static const char kHextable[] = "0123456789abcdef"; |
| for (unsigned i = 0; i < digest_len; i++) { |
| const uint8_t b = digest[i]; |
| hex_digest[i * 2] = kHextable[b >> 4]; |
| hex_digest[i * 2 + 1] = kHextable[b & 0xf]; |
| } |
| *out_hex = std::string(hex_digest, digest_len * 2); |
| |
| return true; |
| } |
| |
| // PrintFileSum hashes |source| with |md| and prints a line to stdout in the |
| // format of the coreutils *sum utilities. It returns true on success or prints |
| // an error to stderr and returns false on error. |
| static bool PrintFileSum(const EVP_MD *md, const Source &source) { |
| std::string hex_digest; |
| if (!SumFile(&hex_digest, md, source)) { |
| return false; |
| } |
| |
| // TODO: When given "--binary" or "-b", we should print " *" instead of " " |
| // between the digest and the filename. |
| // |
| // MSYS and Cygwin md5sum default to binary mode by default, whereas other |
| // platforms' tools default to text mode by default. We default to text mode |
| // by default and consider text mode equivalent to binary mode (i.e. we |
| // always use Unix semantics, even on Windows), which means that our default |
| // output will differ from the MSYS and Cygwin tools' default output. |
| printf("%s %s\n", hex_digest.c_str(), |
| source.is_stdin() ? "-" : source.filename().c_str()); |
| return true; |
| } |
| |
| // CheckModeArguments contains arguments for the check mode. See the |
| // sha256sum(1) man page for details. |
| struct CheckModeArguments { |
| bool quiet = false; |
| bool status = false; |
| bool warn = false; |
| bool strict = false; |
| }; |
| |
| // Check reads lines from |source| where each line is in the format of the |
| // coreutils *sum utilities. It attempts to verify each hash by reading the |
| // file named in the line. |
| // |
| // It returns true if all files were verified and, if |args.strict|, no input |
| // lines had formatting errors. Otherwise it prints errors to stderr and |
| // returns false. |
| static bool Check(const CheckModeArguments &args, const EVP_MD *md, |
| const Source &source) { |
| std::unique_ptr<FILE, func_delete<FILE, int, fclose>> scoped_file; |
| FILE *file; |
| |
| if (source.is_stdin()) { |
| file = stdin; |
| } else { |
| int fd; |
| if (!OpenFile(&fd, source.filename())) { |
| return false; |
| } |
| |
| file = BORINGSSL_FDOPEN(fd, "rb"); |
| if (!file) { |
| perror("fdopen"); |
| BORINGSSL_CLOSE(fd); |
| return false; |
| } |
| |
| scoped_file = std::unique_ptr<FILE, func_delete<FILE, int, fclose>>(file); |
| } |
| |
| const size_t hex_size = EVP_MD_size(md) * 2; |
| char line[EVP_MAX_MD_SIZE * 2 + 2 /* spaces */ + PATH_MAX + 1 /* newline */ + |
| 1 /* NUL */]; |
| unsigned bad_lines = 0; |
| unsigned parsed_lines = 0; |
| unsigned error_lines = 0; |
| unsigned bad_hash_lines = 0; |
| unsigned line_no = 0; |
| bool ok = true; |
| bool draining_overlong_line = false; |
| |
| for (;;) { |
| line_no++; |
| |
| if (fgets(line, sizeof(line), file) == nullptr) { |
| if (feof(file)) { |
| break; |
| } |
| fprintf(stderr, "Error reading from input.\n"); |
| return false; |
| } |
| |
| size_t len = strlen(line); |
| |
| if (draining_overlong_line) { |
| if (line[len - 1] == '\n') { |
| draining_overlong_line = false; |
| } |
| continue; |
| } |
| |
| const bool overlong = line[len - 1] != '\n' && !feof(file); |
| |
| if (len < hex_size + 2 /* spaces */ + 1 /* filename */ || |
| line[hex_size] != ' ' || |
| line[hex_size + 1] != ' ' || |
| overlong) { |
| bad_lines++; |
| if (args.warn) { |
| fprintf(stderr, "%s: %u: improperly formatted line\n", |
| source.is_stdin() ? kStdinName : source.filename().c_str(), line_no); |
| } |
| if (args.strict) { |
| ok = false; |
| } |
| if (overlong) { |
| draining_overlong_line = true; |
| } |
| continue; |
| } |
| |
| if (line[len - 1] == '\n') { |
| line[len - 1] = 0; |
| len--; |
| } |
| |
| parsed_lines++; |
| |
| // coreutils does not attempt to restrict relative or absolute paths in the |
| // input so nor does this code. |
| std::string calculated_hex_digest; |
| const std::string target_filename(&line[hex_size + 2]); |
| Source target_source; |
| if (target_filename == "-") { |
| // coreutils reads from stdin if the filename is "-". |
| target_source = Source(Source::STDIN); |
| } else { |
| target_source = Source(target_filename); |
| } |
| |
| if (!SumFile(&calculated_hex_digest, md, target_source)) { |
| error_lines++; |
| ok = false; |
| continue; |
| } |
| |
| if (calculated_hex_digest != std::string(line, hex_size)) { |
| bad_hash_lines++; |
| if (!args.status) { |
| printf("%s: FAILED\n", target_filename.c_str()); |
| } |
| ok = false; |
| continue; |
| } |
| |
| if (!args.quiet) { |
| printf("%s: OK\n", target_filename.c_str()); |
| } |
| } |
| |
| if (!args.status) { |
| if (bad_lines > 0 && parsed_lines > 0) { |
| fprintf(stderr, "WARNING: %u line%s improperly formatted\n", bad_lines, |
| bad_lines == 1 ? " is" : "s are"); |
| } |
| if (error_lines > 0) { |
| fprintf(stderr, "WARNING: %u computed checksum(s) did NOT match\n", |
| error_lines); |
| } |
| } |
| |
| if (parsed_lines == 0) { |
| fprintf(stderr, "%s: no properly formatted checksum lines found.\n", |
| source.is_stdin() ? kStdinName : source.filename().c_str()); |
| ok = false; |
| } |
| |
| return ok; |
| } |
| |
| // DigestSum acts like the coreutils *sum utilites, with the given hash |
| // function. |
| static bool DigestSum(const EVP_MD *md, |
| const std::vector<std::string> &args) { |
| bool check_mode = false; |
| CheckModeArguments check_args; |
| bool check_mode_args_given = false; |
| std::vector<Source> sources; |
| |
| auto it = args.begin(); |
| while (it != args.end()) { |
| const std::string &arg = *it; |
| if (!arg.empty() && arg[0] != '-') { |
| break; |
| } |
| |
| it++; |
| |
| if (arg == "--") { |
| break; |
| } |
| |
| if (arg == "-") { |
| // "-" ends the argument list and indicates that stdin should be used. |
| sources.push_back(Source(Source::STDIN)); |
| break; |
| } |
| |
| if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') { |
| for (size_t i = 1; i < arg.size(); i++) { |
| switch (arg[i]) { |
| case 'b': |
| case 't': |
| // Binary/text mode – irrelevent, even on Windows. |
| break; |
| case 'c': |
| check_mode = true; |
| break; |
| case 'w': |
| check_mode_args_given = true; |
| check_args.warn = true; |
| break; |
| default: |
| fprintf(stderr, "Unknown option '%c'.\n", arg[i]); |
| return false; |
| } |
| } |
| } else if (arg == "--binary" || arg == "--text") { |
| // Binary/text mode – irrelevent, even on Windows. |
| } else if (arg == "--check") { |
| check_mode = true; |
| } else if (arg == "--quiet") { |
| check_mode_args_given = true; |
| check_args.quiet = true; |
| } else if (arg == "--status") { |
| check_mode_args_given = true; |
| check_args.status = true; |
| } else if (arg == "--warn") { |
| check_mode_args_given = true; |
| check_args.warn = true; |
| } else if (arg == "--strict") { |
| check_mode_args_given = true; |
| check_args.strict = true; |
| } else { |
| fprintf(stderr, "Unknown option '%s'.\n", arg.c_str()); |
| return false; |
| } |
| } |
| |
| if (check_mode_args_given && !check_mode) { |
| fprintf( |
| stderr, |
| "Check mode arguments are only meaningful when verifying checksums.\n"); |
| return false; |
| } |
| |
| for (; it != args.end(); it++) { |
| sources.push_back(Source(*it)); |
| } |
| |
| if (sources.empty()) { |
| sources.push_back(Source(Source::STDIN)); |
| } |
| |
| bool ok = true; |
| |
| if (check_mode) { |
| for (auto &source : sources) { |
| ok &= Check(check_args, md, source); |
| } |
| } else { |
| for (auto &source : sources) { |
| ok &= PrintFileSum(md, source); |
| } |
| } |
| |
| return ok; |
| } |
| |
| bool MD5Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_md5(), args); |
| } |
| |
| bool SHA1Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_sha1(), args); |
| } |
| |
| bool SHA224Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_sha224(), args); |
| } |
| |
| bool SHA256Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_sha256(), args); |
| } |
| |
| bool SHA384Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_sha384(), args); |
| } |
| |
| bool SHA512Sum(const std::vector<std::string> &args) { |
| return DigestSum(EVP_sha512(), args); |
| } |