2014-07-06 08:13:36 +00:00
|
|
|
#![crate_name = "hashsum"]
|
2014-03-24 23:48:40 +00:00
|
|
|
|
2014-03-25 06:37:28 +00:00
|
|
|
/*
|
|
|
|
* This file is part of the uutils coreutils package.
|
|
|
|
*
|
|
|
|
* (c) Arcterus <arcterus@mail.com>
|
2014-06-22 20:27:43 +00:00
|
|
|
* (c) Vsevolod Velichko <torkvemada@sorokdva.net>
|
2014-06-25 01:42:58 +00:00
|
|
|
* (c) Gil Cottle <gcottle@redtown.org>
|
2014-03-25 06:37:28 +00:00
|
|
|
*
|
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
|
|
|
*/
|
|
|
|
|
2014-12-21 14:54:51 +00:00
|
|
|
extern crate crypto;
|
2014-03-24 23:48:40 +00:00
|
|
|
extern crate getopts;
|
2015-06-05 02:26:13 +00:00
|
|
|
extern crate regex_syntax;
|
2015-05-21 02:45:43 +00:00
|
|
|
extern crate regex;
|
2014-03-24 23:48:40 +00:00
|
|
|
|
2015-11-24 01:00:51 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate uucore;
|
|
|
|
|
2014-03-24 23:48:40 +00:00
|
|
|
use crypto::digest::Digest;
|
2014-06-22 20:27:43 +00:00
|
|
|
use crypto::md5::Md5;
|
|
|
|
use crypto::sha1::Sha1;
|
|
|
|
use crypto::sha2::{Sha224, Sha256, Sha384, Sha512};
|
2015-05-16 17:28:36 +00:00
|
|
|
use regex::Regex;
|
|
|
|
use std::ascii::AsciiExt;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{self, BufRead, BufReader, Read, stdin, Write};
|
|
|
|
use std::path::Path;
|
2014-03-24 23:48:40 +00:00
|
|
|
|
2014-06-22 20:27:43 +00:00
|
|
|
static NAME: &'static str = "hashsum";
|
2015-11-25 09:52:10 +00:00
|
|
|
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
2014-03-24 23:48:40 +00:00
|
|
|
|
2014-06-22 20:27:43 +00:00
|
|
|
fn is_custom_binary(program: &str) -> bool {
|
|
|
|
match program {
|
|
|
|
"md5sum" | "sha1sum"
|
|
|
|
| "sha224sum" | "sha256sum"
|
|
|
|
| "sha384sum" | "sha512sum" => true,
|
|
|
|
_ => false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-31 18:01:41 +00:00
|
|
|
fn detect_algo(program: &str, matches: &getopts::Matches) -> (&'static str, Box<Digest+'static>) {
|
2014-06-22 20:27:43 +00:00
|
|
|
let mut alg: Option<Box<Digest>> = None;
|
|
|
|
let mut name: &'static str = "";
|
|
|
|
match program {
|
2015-01-10 12:18:15 +00:00
|
|
|
"md5sum" => ("MD5", Box::new(Md5::new()) as Box<Digest>),
|
|
|
|
"sha1sum" => ("SHA1", Box::new(Sha1::new()) as Box<Digest>),
|
|
|
|
"sha224sum" => ("SHA224", Box::new(Sha224::new()) as Box<Digest>),
|
|
|
|
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<Digest>),
|
|
|
|
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<Digest>),
|
|
|
|
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<Digest>),
|
2014-06-22 20:27:43 +00:00
|
|
|
_ => {
|
|
|
|
{
|
2015-02-06 15:41:20 +00:00
|
|
|
let mut set_or_crash = |n, val| -> () {
|
2014-06-22 20:27:43 +00:00
|
|
|
if alg.is_some() { crash!(1, "You cannot combine multiple hash algorithms!") };
|
|
|
|
name = n;
|
|
|
|
alg = Some(val);
|
|
|
|
};
|
2015-01-10 12:18:15 +00:00
|
|
|
if matches.opt_present("md5") { set_or_crash("MD5", Box::new(Md5::new())) };
|
|
|
|
if matches.opt_present("sha1") { set_or_crash("SHA1", Box::new(Sha1::new())) };
|
|
|
|
if matches.opt_present("sha224") { set_or_crash("SHA224", Box::new(Sha224::new())) };
|
|
|
|
if matches.opt_present("sha256") { set_or_crash("SHA256", Box::new(Sha256::new())) };
|
|
|
|
if matches.opt_present("sha384") { set_or_crash("SHA384", Box::new(Sha384::new())) };
|
|
|
|
if matches.opt_present("sha512") { set_or_crash("SHA512", Box::new(Sha512::new())) };
|
2014-06-22 20:27:43 +00:00
|
|
|
}
|
|
|
|
if alg.is_none() { crash!(1, "You must specify hash algorithm!") };
|
|
|
|
(name, alg.unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 13:48:07 +00:00
|
|
|
pub fn uumain(args: Vec<String>) -> i32 {
|
2015-05-16 17:28:36 +00:00
|
|
|
let program = &args[0];
|
|
|
|
let binary_name = Path::new(program).file_name().unwrap().to_str().unwrap();
|
2014-03-24 23:48:40 +00:00
|
|
|
|
2014-06-27 04:39:56 +00:00
|
|
|
// Default binary in Windows, text mode otherwise
|
|
|
|
let binary_flag_default = cfg!(windows);
|
|
|
|
|
2015-05-21 02:45:43 +00:00
|
|
|
let mut opts = getopts::Options::new();
|
|
|
|
opts.optflag("b", "binary", &format!("read in binary mode{}", if binary_flag_default { " (default)" } else { "" }));
|
|
|
|
opts.optflag("c", "check", "read hashsums from the FILEs and check them");
|
|
|
|
opts.optflag("", "tag", "create a BSD-style checksum");
|
|
|
|
opts.optflag("t", "text", &format!("read in text mode{}", if binary_flag_default { "" } else { " (default)" }));
|
|
|
|
opts.optflag("q", "quiet", "don't print OK for each successfully verified file");
|
|
|
|
opts.optflag("s", "status", "don't output anything, status code shows success");
|
|
|
|
opts.optflag("", "strict", "exit non-zero for improperly formatted checksum lines");
|
|
|
|
opts.optflag("w", "warn", "warn about improperly formatted checksum lines");
|
|
|
|
opts.optflag("h", "help", "display this help and exit");
|
|
|
|
opts.optflag("V", "version", "output version information and exit");
|
|
|
|
|
|
|
|
if !is_custom_binary(program) {
|
|
|
|
opts.optflag("", "md5", "work with MD5");
|
|
|
|
opts.optflag("", "sha1", "work with SHA1");
|
|
|
|
opts.optflag("", "sha224", "work with SHA224");
|
|
|
|
opts.optflag("", "sha256", "work with SHA256");
|
|
|
|
opts.optflag("", "sha384", "work with SHA384");
|
|
|
|
opts.optflag("", "sha512", "work with SHA512");
|
|
|
|
}
|
|
|
|
|
|
|
|
let matches = match opts.parse(&args[1..]) {
|
2014-03-24 23:48:40 +00:00
|
|
|
Ok(m) => m,
|
2014-06-15 10:50:40 +00:00
|
|
|
Err(f) => crash!(1, "{}", f)
|
2014-03-24 23:48:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if matches.opt_present("help") {
|
2015-05-16 17:28:36 +00:00
|
|
|
usage(program, binary_name, &opts);
|
2014-03-24 23:48:40 +00:00
|
|
|
} else if matches.opt_present("version") {
|
2014-06-22 20:27:43 +00:00
|
|
|
version();
|
2014-03-24 23:48:40 +00:00
|
|
|
} else {
|
2015-05-16 17:28:36 +00:00
|
|
|
let (name, algo) = detect_algo(binary_name, &matches);
|
2014-06-22 20:27:43 +00:00
|
|
|
|
2014-06-27 04:39:56 +00:00
|
|
|
let binary_flag = matches.opt_present("binary");
|
|
|
|
let text_flag = matches.opt_present("text");
|
|
|
|
if binary_flag && text_flag {
|
|
|
|
crash!(1, "cannot set binary and text mode at the same time");
|
|
|
|
}
|
|
|
|
let binary = if binary_flag { true } else if text_flag { false } else { binary_flag_default };
|
2014-03-24 23:48:40 +00:00
|
|
|
let check = matches.opt_present("check");
|
|
|
|
let tag = matches.opt_present("tag");
|
|
|
|
let status = matches.opt_present("status");
|
|
|
|
let quiet = matches.opt_present("quiet") || status;
|
|
|
|
let strict = matches.opt_present("strict");
|
|
|
|
let warn = matches.opt_present("warn") && !status;
|
2014-05-07 23:55:53 +00:00
|
|
|
let files = if matches.free.is_empty() {
|
2014-05-28 06:33:39 +00:00
|
|
|
vec!("-".to_string())
|
2014-05-07 23:55:53 +00:00
|
|
|
} else {
|
|
|
|
matches.free
|
|
|
|
};
|
2014-06-22 20:27:43 +00:00
|
|
|
match hashsum(name, algo, files, binary, check, tag, status, quiet, strict, warn) {
|
2014-06-08 07:56:37 +00:00
|
|
|
Ok(()) => return 0,
|
|
|
|
Err(e) => return e
|
|
|
|
}
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
2014-06-08 07:56:37 +00:00
|
|
|
|
2014-06-12 04:41:53 +00:00
|
|
|
0
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
|
2014-06-22 20:27:43 +00:00
|
|
|
fn version() {
|
2015-05-25 18:50:15 +00:00
|
|
|
pipe_println!("{} {}", NAME, VERSION);
|
2014-06-22 20:27:43 +00:00
|
|
|
}
|
|
|
|
|
2015-05-21 02:45:43 +00:00
|
|
|
fn usage(program: &str, binary_name: &str, opts: &getopts::Options) {
|
|
|
|
let spec = if is_custom_binary(binary_name) {
|
|
|
|
format!(" {} [OPTION]... [FILE]...", program)
|
2014-06-22 20:27:43 +00:00
|
|
|
} else {
|
2015-05-21 02:45:43 +00:00
|
|
|
format!(" {} {{--md5|--sha1|--sha224|--sha256|--sha384|--sha512}} [OPTION]... [FILE]...", program)
|
|
|
|
};
|
|
|
|
|
2015-05-25 18:50:15 +00:00
|
|
|
let msg = format!("{} {}
|
2015-05-21 02:45:43 +00:00
|
|
|
|
|
|
|
Usage:
|
|
|
|
{}
|
|
|
|
|
|
|
|
Compute and check message digests.", NAME, VERSION, spec);
|
|
|
|
|
|
|
|
pipe_print!("{}", opts.usage(&msg));
|
2014-06-22 20:27:43 +00:00
|
|
|
}
|
|
|
|
|
2015-05-16 17:28:36 +00:00
|
|
|
fn hashsum<'a>(algoname: &str, mut digest: Box<Digest+'a>, files: Vec<String>, binary: bool, check: bool, tag: bool, status: bool, quiet: bool, strict: bool, warn: bool) -> Result<(), i32> {
|
2015-01-10 12:18:15 +00:00
|
|
|
let mut bad_format = 0;
|
|
|
|
let mut failed = 0;
|
2014-06-25 01:42:58 +00:00
|
|
|
let binary_marker = if binary {
|
|
|
|
"*"
|
|
|
|
} else {
|
|
|
|
" "
|
|
|
|
};
|
2014-03-24 23:48:40 +00:00
|
|
|
for filename in files.iter() {
|
2015-05-16 17:28:36 +00:00
|
|
|
let filename: &str = filename;
|
2015-07-09 02:24:02 +00:00
|
|
|
let stdin_buf;
|
|
|
|
let file_buf;
|
2015-05-16 17:28:36 +00:00
|
|
|
let mut file = BufReader::new(
|
2014-05-23 12:28:40 +00:00
|
|
|
if filename == "-" {
|
2015-05-16 17:28:36 +00:00
|
|
|
stdin_buf = stdin();
|
|
|
|
Box::new(stdin_buf) as Box<Read>
|
2014-05-07 23:55:53 +00:00
|
|
|
} else {
|
2015-05-16 17:28:36 +00:00
|
|
|
file_buf = safe_unwrap!(File::open(filename));
|
|
|
|
Box::new(file_buf) as Box<Read>
|
2014-05-07 23:55:53 +00:00
|
|
|
}
|
|
|
|
);
|
2014-03-24 23:48:40 +00:00
|
|
|
if check {
|
2014-06-25 03:26:24 +00:00
|
|
|
// Set up Regexes for line validation and parsing
|
2014-06-25 01:42:58 +00:00
|
|
|
let bytes = digest.output_bits() / 4;
|
2014-06-25 03:26:24 +00:00
|
|
|
let gnu_re = safe_unwrap!(
|
|
|
|
Regex::new(
|
2015-05-16 17:28:36 +00:00
|
|
|
&format!(
|
2014-06-25 01:42:58 +00:00
|
|
|
r"^(?P<digest>[a-fA-F0-9]{{{}}}) (?P<binary>[ \*])(?P<fileName>.*)",
|
|
|
|
bytes
|
2015-05-16 17:28:36 +00:00
|
|
|
)
|
2014-06-25 03:26:24 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let bsd_re = safe_unwrap!(
|
|
|
|
Regex::new(
|
2015-05-16 17:28:36 +00:00
|
|
|
&format!(
|
2014-06-25 01:42:58 +00:00
|
|
|
r"^{algorithm} \((?P<fileName>.*)\) = (?P<digest>[a-fA-F0-9]{{{digest_size}}})",
|
|
|
|
algorithm = algoname,
|
|
|
|
digest_size = bytes
|
2015-05-16 17:28:36 +00:00
|
|
|
)
|
2014-06-25 03:26:24 +00:00
|
|
|
)
|
|
|
|
);
|
2014-06-25 01:42:58 +00:00
|
|
|
|
2015-05-16 17:28:36 +00:00
|
|
|
let buffer = file;
|
2014-03-24 23:48:40 +00:00
|
|
|
for (i, line) in buffer.lines().enumerate() {
|
|
|
|
let line = safe_unwrap!(line);
|
2015-05-16 17:28:36 +00:00
|
|
|
let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) {
|
2014-12-21 23:51:19 +00:00
|
|
|
Some(caps) => (caps.name("fileName").unwrap(),
|
2014-12-30 19:11:06 +00:00
|
|
|
caps.name("digest").unwrap().to_ascii_lowercase(),
|
2014-12-21 23:51:19 +00:00
|
|
|
caps.name("binary").unwrap() == "*"),
|
2015-05-16 17:28:36 +00:00
|
|
|
None => match bsd_re.captures(&line) {
|
2014-12-21 23:51:19 +00:00
|
|
|
Some(caps) => (caps.name("fileName").unwrap(),
|
2014-12-30 19:11:06 +00:00
|
|
|
caps.name("digest").unwrap().to_ascii_lowercase(),
|
2014-12-21 23:51:19 +00:00
|
|
|
true),
|
2014-03-24 23:48:40 +00:00
|
|
|
None => {
|
|
|
|
bad_format += 1;
|
|
|
|
if strict {
|
2014-06-08 07:56:37 +00:00
|
|
|
return Err(1);
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
if warn {
|
2014-06-22 20:27:43 +00:00
|
|
|
show_warning!("{}: {}: improperly formatted {} checksum line", filename, i + 1, algoname);
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-05-16 17:28:36 +00:00
|
|
|
let f = safe_unwrap!(File::open(ck_filename));
|
|
|
|
let mut ckf = BufReader::new(Box::new(f) as Box<Read>);
|
2014-12-30 19:11:06 +00:00
|
|
|
let real_sum = safe_unwrap!(digest_reader(&mut digest, &mut ckf, binary_check))
|
2015-05-16 17:28:36 +00:00
|
|
|
.to_ascii_lowercase();
|
|
|
|
if sum == real_sum {
|
2014-03-24 23:48:40 +00:00
|
|
|
if !quiet {
|
2014-07-25 05:20:03 +00:00
|
|
|
pipe_println!("{}: OK", ck_filename);
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !status {
|
2014-07-25 05:20:03 +00:00
|
|
|
pipe_println!("{}: FAILED", ck_filename);
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
failed += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2014-06-27 04:39:56 +00:00
|
|
|
let sum = safe_unwrap!(digest_reader(&mut digest, &mut file, binary));
|
2014-03-24 23:48:40 +00:00
|
|
|
if tag {
|
2014-07-25 05:20:03 +00:00
|
|
|
pipe_println!("{} ({}) = {}", algoname, filename, sum);
|
2014-03-24 23:48:40 +00:00
|
|
|
} else {
|
2014-07-25 05:20:03 +00:00
|
|
|
pipe_println!("{} {}{}", sum, binary_marker, filename);
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !status {
|
|
|
|
if bad_format == 1 {
|
|
|
|
show_warning!("{} line is improperly formatted", bad_format);
|
|
|
|
} else if bad_format > 1 {
|
|
|
|
show_warning!("{} lines are improperly formatted", bad_format);
|
|
|
|
}
|
|
|
|
if failed > 0 {
|
|
|
|
show_warning!("{} computed checksum did NOT match", failed);
|
|
|
|
}
|
|
|
|
}
|
2014-06-08 07:56:37 +00:00
|
|
|
|
2014-06-12 04:41:53 +00:00
|
|
|
Ok(())
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
|
|
|
|
2015-05-16 17:28:36 +00:00
|
|
|
fn digest_reader<'a, T: Read>(digest: &mut Box<Digest+'a>, reader: &mut BufReader<T>, binary: bool) -> io::Result<String> {
|
2014-06-22 20:27:43 +00:00
|
|
|
digest.reset();
|
2014-06-27 04:39:56 +00:00
|
|
|
|
|
|
|
// Digest file, do not hold too much in memory at any given moment
|
|
|
|
let windows = cfg!(windows);
|
2015-05-16 17:28:36 +00:00
|
|
|
let mut buffer = Vec::with_capacity(524288);
|
2014-06-27 04:39:56 +00:00
|
|
|
let mut vec = Vec::with_capacity(524288);
|
|
|
|
let mut looking_for_newline = false;
|
|
|
|
loop {
|
2015-05-16 17:28:36 +00:00
|
|
|
match reader.read_to_end(&mut buffer) {
|
|
|
|
Ok(0) => { break; },
|
2014-06-27 04:39:56 +00:00
|
|
|
Ok(nread) => {
|
|
|
|
if windows && !binary {
|
|
|
|
// Windows text mode returns '\n' when reading '\r\n'
|
2015-05-16 17:28:36 +00:00
|
|
|
for i in 0 .. nread {
|
2014-06-27 04:39:56 +00:00
|
|
|
if looking_for_newline {
|
|
|
|
if buffer[i] != ('\n' as u8) {
|
|
|
|
vec.push('\r' as u8);
|
|
|
|
}
|
|
|
|
if buffer[i] != ('\r' as u8) {
|
|
|
|
vec.push(buffer[i]);
|
|
|
|
looking_for_newline = false;
|
|
|
|
}
|
|
|
|
} else if buffer[i] != ('\r' as u8) {
|
|
|
|
vec.push(buffer[i]);
|
|
|
|
} else {
|
|
|
|
looking_for_newline = true;
|
|
|
|
}
|
|
|
|
}
|
2015-05-16 17:28:36 +00:00
|
|
|
digest.input(&vec);
|
2014-06-27 04:39:56 +00:00
|
|
|
vec.clear();
|
|
|
|
} else {
|
2015-01-24 09:44:48 +00:00
|
|
|
digest.input(&buffer[..nread]);
|
2014-06-27 04:39:56 +00:00
|
|
|
}
|
|
|
|
},
|
2015-05-16 17:28:36 +00:00
|
|
|
Err(e) => return Err(e)
|
2014-06-27 04:39:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if windows && looking_for_newline {
|
|
|
|
vec.push('\r' as u8);
|
2015-05-16 17:28:36 +00:00
|
|
|
digest.input(&vec);
|
2014-06-27 04:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(digest.result_str())
|
2014-03-24 23:48:40 +00:00
|
|
|
}
|
2015-11-22 20:45:04 +00:00
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
fn main() {
|
|
|
|
std::process::exit(uumain(std::env::args().collect()));
|
|
|
|
}
|