diff --git a/src/release.rs b/src/release.rs index fe79dbb..6f02afc 100644 --- a/src/release.rs +++ b/src/release.rs @@ -38,6 +38,7 @@ pub struct RequestedRelease { pub codename: String, pub arches: Vec, + pub untrusted: bool, } #[derive(Debug, Clone)] @@ -139,6 +140,7 @@ impl RequestedReleases { mirror: Url::parse(&entry.url)?, codename: entry.suite_codename.to_string(), arches: arches.to_vec(), + untrusted: entry.untrusted, }) { hash_map::Entry::Vacant(vacancy) => { vacancy.insert(vec![entry.clone()]); @@ -173,7 +175,7 @@ impl RequestedReleases { &dest, )], ) { - Ok(_) => gpg.verify_clearsigned(&dest, &verified), + Ok(_) => gpg.read_clearsigned(&dest, &verified, !release.untrusted), Err(_) => { let mut detatched_signature = dest.as_os_str().to_os_string(); detatched_signature.push(".gpg"); @@ -183,14 +185,18 @@ impl RequestedReleases { &[Download::from_to(release.dists()?.join("Release")?, &dest)], )?; - fetch( - client, - &[Download::from_to( - release.dists()?.join("Release.gpg")?, - &detatched_signature, - )], - )?; - gpg.verify_detached(&dest, detatched_signature, verified) + if !release.untrusted { + fetch( + client, + &[Download::from_to( + release.dists()?.join("Release.gpg")?, + &detatched_signature, + )], + )?; + gpg.verify_detached(&dest, detatched_signature, verified) + } else { + Ok(()) + } } } .with_context(|| anyhow!("verifying {:?} at {:?}", release, dest))?; diff --git a/src/signing.rs b/src/signing.rs index c1a7f3c..a1e2f8f 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -17,10 +17,11 @@ impl<'k> GpgClient<'k> { GpgClient { keyring } } - pub fn verify_clearsigned, Q: AsRef>( + pub fn read_clearsigned, Q: AsRef>( &self, file: P, dest: Q, + verify: bool, ) -> Result<(), Error> { let from = fs::File::open(file).with_context(|| anyhow!("opening input file"))?; let to = PersistableTempFile::new_in( @@ -30,7 +31,12 @@ impl<'k> GpgClient<'k> { ) .with_context(|| anyhow!("creating temporary file"))?; - gpgrv::verify_message(io::BufReader::new(from), &to, &self.keyring)?; + let reader = io::BufReader::new(from); + if verify { + gpgrv::verify_message(reader, &to, &self.keyring)?; + } else { + gpgrv::read_doc(reader, &to)?; + } to.persist_by_rename(dest) .map_err(|e| e.error) diff --git a/src/sources_list.rs b/src/sources_list.rs index 91de552..0e2a876 100644 --- a/src/sources_list.rs +++ b/src/sources_list.rs @@ -15,6 +15,38 @@ pub struct Entry { pub suite_codename: String, pub components: Vec, pub arch: Option, + pub untrusted: bool, +} + +#[derive(Default)] +struct ParsedOpts { + arch: Option, + untrusted: Option, +} + +fn parse_opts(opts: Option<&str>) -> Result { + let mut ret = ParsedOpts::default(); + if let Some(opts) = opts { + if opts.contains(" ") { + bail!("only one option per line supported") + } + let parts: Vec<_> = opts + .strip_prefix("[") + .expect("opening [") + .strip_suffix("]") + .expect("closing ]") + .split("=") + .collect(); + match parts.len() { + 2 => match parts[0] { + "arch" => ret.arch = Some(parts[1].to_string()), + "untrusted" => ret.untrusted = Some(parts[1] == "yes"), + other => bail!("unknown option: {}", other), + }, + _ => bail!("multiple = in option"), + } + }; + Ok(ret) } fn read_single_line(line: &str) -> Result, Error> { @@ -33,7 +65,7 @@ fn read_single_line(line: &str) -> Result, Error> { let src = parts .next() .ok_or_else(|| anyhow!("deb{{,s,-src}} section required"))?; - let arch = match parts.peek() { + let opts = match parts.peek() { Some(&val) if val.starts_with("[") => { parts.next(); Some(val) @@ -60,6 +92,7 @@ fn read_single_line(line: &str) -> Result, Error> { let mut ret = Vec::with_capacity(srcs.len()); + let parsed_opts = parse_opts(opts)?; for src in srcs { ret.push(Entry { src: *src, @@ -70,7 +103,8 @@ fn read_single_line(line: &str) -> Result, Error> { }, suite_codename: suite.to_string(), components: components.iter().map(|x| x.to_string()).collect(), - arch: arch.map(|arch| arch.to_string()), + arch: parsed_opts.arch.clone(), + untrusted: parsed_opts.untrusted.unwrap_or(false), }); } @@ -113,6 +147,7 @@ mod tests { url: "http://foo/".to_string(), suite_codename: "bar".to_string(), components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, }, Entry { src: true, @@ -120,12 +155,73 @@ mod tests { url: "http://foo/".to_string(), suite_codename: "bar".to_string(), components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, }, ], read(io::Cursor::new( r" deb http://foo bar baz quux deb-src http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn arch() { + assert_eq!( + vec![Entry { + src: false, + arch: Some("amd64".to_string()), + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, + },], + read(io::Cursor::new( + r" +deb [arch=amd64] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn untrusted() { + assert_eq!( + vec![Entry { + src: false, + arch: None, + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: true, + },], + read(io::Cursor::new( + r" +deb [untrusted=yes] http://foo bar baz quux +", + )) + .unwrap() + ); + } + + #[test] + fn untrusted_not_yes() { + assert_eq!( + vec![Entry { + src: false, + arch: None, + url: "http://foo/".to_string(), + suite_codename: "bar".to_string(), + components: vec!["baz".to_string(), "quux".to_string()], + untrusted: false, + },], + read(io::Cursor::new( + r" +deb [untrusted=yeppers] http://foo bar baz quux ", )) .unwrap()