|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- /* 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
- #pragma warning(push, 3)
- #include <windows.h>
- #pragma warning(pop)
- #include <io.h>
- #define PATH_MAX MAX_PATH
- typedef int ssize_t;
- #endif
-
- #include <openssl/digest.h>
-
-
- struct close_delete {
- void operator()(int *fd) {
- 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 = 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 = 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 = fdopen(fd, "rb");
- if (!file) {
- perror("fdopen");
- 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);
- }
|