#[macro_use]
extern crate clap;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate log;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate maplit;
extern crate digest;
extern crate hex;
extern crate md5;
extern crate reqwest;
extern crate sha1;
extern crate sha2;
extern crate sha3;
extern crate simplelog;
mod cli;
mod hashing;
mod read;
use digest::DynDigest;
use failure::Error;
use hashing::{digest, hex_bits, HashType};
use hex::encode;
use log::Level;
use read::download;
use simplelog::{Config, LevelFilter, TermLogger};
use std::io::Write;
use std::process::{Command, ExitStatus, Stdio};
fn hash_instance(algo: &str, sum: &Option<&str>) -> Result<Box<DynDigest>, Error> {
let algo = match algo {
"MD5" => {
warn!("MD5 is not considered secure, please use a more secure checksum algorithm if possible");
HashType::MD5
}
"SHA1" => {
warn!("SHA-1 is not considered secure, please use a more secure checksum algorithm if possible");
HashType::SHA1
}
"SHA2" => HashType::SHA2,
"SHA3" => HashType::SHA3,
_ => bail!("No checksum provided to verify against"),
};
let bits = match sum {
Some(x) => Some(hex_bits(x)?),
None => None,
};
digest(algo, bits)
}
fn compute_hash(mut digest: Box<DynDigest>, body: &[u8]) -> String {
digest.input(body);
let computed = encode(digest.result());
info!("Hash: {}", &computed);
computed
}
fn hash_matches(computed: &str, expected: &str) -> bool {
computed.eq_ignore_ascii_case(expected)
}
fn exec(command: &str, downloaded: &[u8]) -> Result<ExitStatus, Error> {
info!("Starting command '{}'", command);
let mut child = Command::new(command)
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.spawn()?;
{
debug!("Piping in contents to command");
let stdin = child.stdin.as_mut().expect("Cannot open stdin");
stdin.write_all(downloaded)?;
}
let status = child.wait()?;
debug!("Finished running with exit status: {}", status);
Ok(status)
}
fn setup_logging(verbosity: u64) -> Result<(), simplelog::TermLogError> {
let mut config = Config::default();
config.time = Some(Level::Debug);
let log_level = match verbosity {
0 => LevelFilter::Error,
1 => LevelFilter::Info, 2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
TermLogger::init(log_level, config)
}
fn run(args: clap::ArgMatches) -> Result<Option<ExitStatus>, Error> {
if let (command, Some(args)) = args.subcommand() {
let url = args.value_of("URL");
let algo = args.value_of("ALGO");
let checksum = args.value_of("HASH");
let digest = hash_instance(&algo.unwrap(), &checksum)?;
if url.is_none() {
bail!("No URL/filename given")
}
let data = download(&url.unwrap())?;
let computed = compute_hash(digest, &data);
match command {
"hash" => {
println!("{} {} {}", &url.unwrap(), &algo.unwrap(), &computed);
return Ok(None);
}
"verify" if hash_matches(&computed, &checksum.unwrap()) => {
info!("Checksum matches");
return Ok(None);
}
"run" if hash_matches(&computed, &checksum.unwrap()) => {
info!("Checksum matches");
return Ok(Some(exec("sh", &data)?));
}
_ => {
bail!("Checksum does not match");
}
}
} else {
bail!("No valid subcommand given");
}
}
#[test]
fn verify_matches() {
let hashes = vec![
vec!["MD5", "be92ab994901c38365cf28a8874fc7c3"],
vec!["SHA1", "b8aab367f895494d8452a5e89ccfa2b0acb13e90"],
vec!["SHA2", "ac153c840ff6b48853eb5dca8ff3f5f4f48a7c5e73cc2ef9f50ec672ad670c22612492eae3b7100f51e3f5900ce18cb3ebabe5dbd9fb514d78b3cfa7306165ba"],
vec!["SHA3", "8844273dccb5f098a14de9cd3cdf250f87693713e6911bcb103545edadae5d7965c14107f238e6e66847f38f471894c007b3cc862f794275809032bfe83d182c"],
];
for hash in hashes {
let cli = cli::args();
let mut args = vec!["pincers", "verify", "tests/fixtures/echo.sh"];
args.extend_from_slice(&hash);
assert!(run(cli.get_matches_from(args)).unwrap().is_none());
}
}
#[test]
fn verify_not_matches() {
let hashes = vec![
vec!["MD5", "0"],
vec!["MD5", "00000000000000000000000000000000"],
vec!["SHA1", "0000000000000000000000000000000000000000"],
vec!["SHA2", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
vec!["SHA3", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"],
];
for hash in hashes {
let cli = cli::args();
let mut args = vec!["pincers", "verify", "tests/fixtures/echo.sh"];
args.extend_from_slice(&hash);
assert!(run(cli.get_matches_from(args)).is_err());
}
}
fn main() {
let args = cli::args().get_matches();
setup_logging(args.occurrences_of("v")).expect("Could not setup logging");
let status = run(args);
let exit_code = match status {
Err(err) => {
error!("{}", err);
1
}
Ok(Some(exit_status)) => exit_status.code().unwrap_or(1),
Ok(None) => 0,
};
std::process::exit(exit_code)
}