extern crate std;
extern crate tempdir;
extern crate crypto;
use config;
use compiler::parser;
use compiler::parser::PropertyValue;
use fetcher;
use subprocess;
use unpacker;
use crypto::digest::Digest;
use std::io::Read;
const METHOD_NAME: &'static str = "artifact";
pub struct Method {
urls: Vec<String>,
md5: Option<String>,
sha1: Option<String>,
sha256: Option<String>,
sha512: Option<String>,
pgp_pubkey: Option<String>,
pgp_signature: Option<String>,
unpackers: Vec<Box<unpacker::Unpacker>>,
need_integrity_checking: bool,
}
impl Method {
fn check_pgp_signature(&self, download: &std::path::Path) -> Result<bool, subprocess::Error> {
if self.pgp_pubkey.is_some() && self.pgp_signature.is_some() {
let pubkey = self.pgp_pubkey.as_ref().unwrap();
let signature = self.pgp_signature.as_ref().unwrap();
let mut check = subprocess::new("gpg");
check.stdout(std::process::Stdio::null());
check.arg("--recv-key");
check.arg(pubkey);
subprocess::run(&mut check)?;
let tmp_dir = tempdir::TempDir::new("pgp-dl")?;
let signature_file = tmp_dir.path().join("signature.sig");
let mut dl = subprocess::new("curl");
dl.stdout(std::process::Stdio::null());
dl.arg("--silent");
dl.arg("--show-error");
dl.arg("--output");
dl.arg(&signature_file);
dl.arg(signature);
subprocess::run(&mut dl)?;
let mut cmd = subprocess::new("gpg");
cmd.stdout(std::process::Stdio::null());
cmd.arg("--verify");
cmd.arg(&signature_file);
cmd.arg(download);
subprocess::run(&mut cmd)?;
}
Ok(true)
}
fn check_md5(&self, file: &[u8]) -> bool {
if let Some(ref expected) = self.md5 {
debug!("Checking that md5 is {}", expected);
let mut digest = crypto::md5::Md5::new();
digest.input(file);
let mut hash = vec![0u8; digest.output_bits() / 8];
digest.result(hash.as_mut_slice());
let hash_str = format!("{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}",
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5], hash[6], hash[7],
hash[8], hash[9], hash[10], hash[11],
hash[12], hash[13], hash[14], hash[15]);
if *expected != hash_str {
return false;
} else {
trace!("MD5 {} matches {}", expected, hash_str);
}
}
true
}
fn check_sha1(&self, file: &[u8]) -> bool {
if let Some(ref expected) = self.sha1 {
debug!("Checking that sha1 is {}", expected);
let mut digest = crypto::sha1::Sha1::new();
digest.input(file);
let mut hash = vec![0u8; digest.output_bits() / 8];
digest.result(hash.as_mut_slice());
let hash_str = format!("{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}",
hash[0], hash[1], hash[2], hash[3], hash[4],
hash[5], hash[6], hash[7], hash[8], hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14],
hash[15], hash[16], hash[17], hash[18], hash[19]);
if *expected != hash_str {
error!("SHA1 mismatch: {}. Expected: {}.", hash_str, expected);
return false
} else {
trace!("SHA1 {} matches {}", expected, hash_str);
}
}
true
}
fn check_sha256(&self, file: &[u8]) -> bool {
if let Some(ref expected) = self.sha256 {
debug!("Checking that sha256 is {}", expected);
let mut digest = crypto::sha2::Sha256::new();
digest.input(file);
let mut hash = vec![0u8; digest.output_bits() / 8];
digest.result(hash.as_mut_slice());
let hash_str = format!("{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}",
hash[0], hash[1], hash[2], hash[3], hash[4],
hash[5], hash[6], hash[7], hash[8], hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14],
hash[15], hash[16], hash[17], hash[18], hash[19],
hash[20], hash[21], hash[22], hash[23], hash[24],
hash[25], hash[26], hash[27], hash[28], hash[29],
hash[30], hash[31]
);
if *expected != hash_str {
error!("SHA256 mismatch: {}. Expected: {}.", hash_str, expected);
return false
} else {
trace!("SHA256 {} matches {}", expected, hash_str);
}
}
true
}
fn check_sha512(&self, file: &[u8]) -> bool {
if let Some(ref expected) = self.sha512 {
debug!("Checking that sha512 is {}", expected);
let mut digest = crypto::sha2::Sha512::new();
digest.input(file);
let mut hash = vec![0u8; digest.output_bits() / 8];
digest.result(hash.as_mut_slice());
let hash_str = format!("{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}{:02x}\
{:02x}{:02x}{:02x}{:02x}",
hash[0], hash[1], hash[2], hash[3], hash[4],
hash[5], hash[6], hash[7], hash[8], hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14],
hash[15], hash[16], hash[17], hash[18], hash[19],
hash[20], hash[21], hash[22], hash[23], hash[24],
hash[25], hash[26], hash[27], hash[28], hash[29],
hash[30], hash[31], hash[32], hash[33], hash[34],
hash[35], hash[36], hash[37], hash[38], hash[39],
hash[40], hash[41], hash[42], hash[43], hash[44],
hash[45], hash[46], hash[47], hash[48], hash[49],
hash[50], hash[51], hash[52], hash[53], hash[54],
hash[55], hash[56], hash[57], hash[58], hash[59],
hash[60], hash[61], hash[62], hash[63]
);
if *expected != hash_str {
error!("SHA512 mismatch: {}. Expected: {}.", hash_str, expected);
return false
} else {
trace!("SHA512 {} matches {}", expected, hash_str);
}
}
true
}
fn download(&self, component: &config::Component, out_path: &std::path::Path) -> Result<std::path::PathBuf, fetcher::Error> {
for url in &self.urls {
trace!("Downloading from {}", url);
let file = match url.rfind('/') {
Some(index) => { &url[index+1..] },
None => {
error!("Failed to determine the file to be downloaded");
continue;
}
};
let mut output_path = std::path::PathBuf::new();
output_path.push(out_path);
output_path.push(file);
let mut cmd = subprocess::new("curl");
cmd.stdout(std::process::Stdio::null());
cmd.arg("--silent");
cmd.arg("--show-error");
cmd.arg("--output");
cmd.arg(&output_path);
cmd.arg(url);
if subprocess::run(&mut cmd).is_ok() {
let mut hash_failed = 0;
if self.need_integrity_checking {
let mut file = std::fs::File::open(&output_path)?;
let mut file_data: Vec<u8> = Vec::new();
file.read_to_end(&mut file_data)?;
if ! self.check_md5(&file_data) {
hash_failed += 1;
}
if ! self.check_sha1(&file_data) {
hash_failed += 1;
}
if ! self.check_sha256(&file_data) {
hash_failed += 1;
}
if ! self.check_sha512(&file_data) {
hash_failed += 1;
}
if ! self.check_pgp_signature(&output_path)? {
hash_failed += 1;
}
}
if hash_failed == 0 {
return Ok(output_path);
} else {
error!("{} hashing check(s) failed", hash_failed);
}
}
error!("Failed to fetch coherent artifact \"{}\" for url \"{}\"",
component.id_get(), url);
}
Err(fetcher::Error::EverythingFailed)
}
}
impl fetcher::Method for Method {
fn fetch_new(&self, component: &config::Component) -> Result<(), fetcher::Error> {
let mut dl_path = std::path::PathBuf::new();
let component_path = std::path::Path::new(component.path_get());
if ! component_path.is_absolute() {
dl_path.push(std::env::current_dir()?);
}
dl_path.push(component_path);
dl_path.pop();
let mut file = self.download(component, dl_path.as_path())?;
for unpacker in &self.unpackers {
let canon_file = std::fs::canonicalize(file)?;
file = unpacker.unpack(canon_file.as_path(), dl_path.as_path())?;
}
if ! component_path.exists() {
error!("The fetch operation did not create the specified component \
path {:?}", component_path);
error!("This may happen because unpacking resulted in a \
different path than the one expected.");
Err(fetcher::Error::ComponentPathNotCreated)
} else {
Ok(())
}
}
fn fetch_update(&self, component: &config::Component, force: bool) -> Result<(), fetcher::Error> {
if force {
self.fetch_new(component)
} else {
Ok(())
}
}
#[allow(unused_variables)]
fn is_fetchable(&self, component: &config::Component) -> bool {
true
}
fn name_get(&self) -> &str {
METHOD_NAME
}
}
fn parse_hash_algorithm(component: &str, cfg: &config::Config, algo: &str) -> Result<Option<String>, parser::Error> {
if let Some(prop) = cfg.get_fetch_property(component, METHOD_NAME, algo) {
match prop {
PropertyValue::StringValue(val) => { return Ok(Some(val.clone())); },
_ => { return Err(parser::Error::InvalidPropertyType); }
}
}
Ok(None)
}
fn parse_pgp_pubkey(component: &str, cfg: &config::Config) -> Result<Option<String>, parser::Error> {
if let Some(prop) = cfg.get_fetch_property(component, METHOD_NAME, "pgp-pubkey") {
match prop {
PropertyValue::StringValue(val) => { return Ok(Some(val.clone())); },
_ => { return Err(parser::Error::InvalidPropertyType); }
}
}
Ok(None)
}
fn parse_pgp_signature(component: &str, cfg: &config::Config) -> Result<Option<String>, parser::Error> {
if let Some(prop) = cfg.get_fetch_property(component, METHOD_NAME, "pgp-signature") {
match prop {
PropertyValue::StringValue(val) => { return Ok(Some(val.clone())); },
_ => { return Err(parser::Error::InvalidPropertyType); }
}
}
Ok(None)
}
fn parse_compression(component: &str, cfg: &config::Config) -> Result<Vec<Box<unpacker::Unpacker>>, parser::Error> {
if let Some(prop) = cfg.get_fetch_property(component, METHOD_NAME, "compression") {
match prop {
PropertyValue::StringListValue(val) => {
match unpacker::get_for_archive(val) {
Ok(unpackers) => { return Ok(unpackers); },
Err(_) => { return Err(parser::Error::InvalidUnpackMethod); }
}
},
_ => { return Err(parser::Error::InvalidPropertyType); },
}
}
Ok(Vec::new())
}
pub fn parse(component: &str, cfg: &config::Config) -> Result<Box<fetcher::Method>, parser::Error> {
let urls = try!(fetcher::parse_url(component, METHOD_NAME, cfg));
let md5 = try!(parse_hash_algorithm(component, cfg, "md5"));
let sha1 = try!(parse_hash_algorithm(component, cfg, "sha1"));
let sha256 = try!(parse_hash_algorithm(component, cfg, "sha256"));
let sha512 = try!(parse_hash_algorithm(component, cfg, "sha512"));
let pgp_pubkey = try!(parse_pgp_pubkey(component, cfg));
let pgp_signature = try!(parse_pgp_signature(component, cfg));
let unpackers = try!(parse_compression(component, cfg));
let mut need_integrity_checking = false;
if (pgp_pubkey.is_some() || pgp_signature.is_some()) &&
(pgp_pubkey.is_none() || pgp_signature.is_none()) {
error!("pgp-pubkey and pgp-signature must be used together");
return Err(parser::Error::MissingRequiredProperty);
}
if md5.is_some() || sha1.is_some() || sha256.is_some() || sha512.is_some() ||
(pgp_pubkey.is_some() && pgp_signature.is_some()) {
need_integrity_checking = true;
}
Ok(Box::new(Method {
urls: urls,
md5: md5,
sha1: sha1,
sha256: sha256,
sha512: sha512,
pgp_pubkey: pgp_pubkey,
pgp_signature: pgp_signature,
unpackers: unpackers,
need_integrity_checking: need_integrity_checking,
}))
}