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